HTTP-API
laya-serve stellt Laya über das TypeSafe-Jev-Wire-Protokoll /v1/systemone bereit. Ein gegen Jev geschriebener
Client – hs-jev, typesafe-sdk oder dein eigener – kann seine Basis-URL auf diesen Server richten und
weiterarbeiten: Die Ausgabe von predict() aus Laya ist bereits schema-kompatibel, und der Server fügt nur die
HTTP-Oberfläche hinzu: eine Entscheidungsroute, eine Health-Sonde, eine optionale Bearer-Prüfung und Anfragegrenzen.
Ein Client, der sich an laya-serve statt an die Jev-API richtet, existiert für PHP: marcreichel/laya-php ist ein Composer-SDK (PHP 8.4+), das eine Klasse von Enums und Attributen auf Fragen abbildet und eine Instanz zurückgibt, GET /health für eine Deploy-Prüfung liest und ein Test-Fake mitliefert, damit Aufrufer ohne laufenden Server unit-testen können.
pip install "laya[serve]"
laya-serve # http://0.0.0.0:8000
Derselbe Einstiegspunkt läuft eingebettet in jedem ASGI-Server: laya.serve.create_app() baut die FastAPI-App,
optional mit einem Router, den du injizierst (create_app(router)), statt einem aus der Umgebung gebauten.
Konfiguration
Alles sind Umgebungsvariablen, sodass ein Image sowohl einen Entwicklungsstart auf dem Laptop als auch eine systemd-Unit bedient.
| Umgebungsvariable | Bedeutung | Standard |
|---|---|---|
LAYA_HOST |
Bind-Adresse | 0.0.0.0 |
LAYA_PORT |
Bind-Port | 8000 |
LAYA_ROOT_PATH |
öffentlicher URL-Präfix, wenn hinter einem Reverse-Proxy bereitgestellt | leer |
LAYA_DEVICE |
torch-Gerät für jeden Checkpoint | auto |
LAYA_PRELOAD |
baut die Checkpoints beim Start, nicht lazy | 1 |
LAYA_MODELS |
Kommaliste zum Vorladen (english,multilingual,typed-decisions); leer = alle |
alle |
LAYA_THREADS |
begrenzt die Intra-Op-Threads von torch auf der CPU; halte es <= den physischen Kernen – die logischen Kerne zu überbuchen ist eine große Regression | torch-Standard |
LAYA_AUTO_TASK |
automatisch zum Checkpoint typed-decisions routen | 0 |
LAYA_IDLE_UNLOAD_SECONDS |
entlädt residente Checkpoints nach so vielen Idle-Sekunden; die nächste Anfrage lädt ihren Checkpoint erneut. Null deaktiviert das Entladen | 0 |
LAYA_DEFAULT_MODEL |
Checkpoint, auf den ein Zustand ohne Sprachhinweis zurückfällt; Aliase wie ml werden so aufgelöst, wie core sie auflöst, und ein nicht auflösbarer Name stoppt den Server beim Start |
english |
LAYA_API_KEY |
wenn gesetzt, erfordert Authorization: Bearer <key> |
keiner |
LAYA_LOG_LEVEL |
uvicorn-Loglevel | info |
LAYA_MAX_CONCURRENT |
gleichzeitig nach der Authentifizierung zugelassene Anfragen; Überschuss bekommt 503 |
16 |
LAYA_MAX_BATCH_TOKENS |
Token, die ein FORWARD PASS eines /v1/systemone/batch zusammenfassen darf (states x Fragen x Zeilenbreite); ein größerer Batch wird auf mehrere Pässe aufgeteilt, nicht abgelehnt |
131072 |
LAYA_JEV_STRICT |
liefert den strikten Jev-Wire-Vertrag: kein Root-routing, kein action / answer_confidence pro Antwort, kein confidence bei noul-Antworten und usage reduziert auf input_tokens + output_tokens. Für Clients, die die Antwort gegen den Jev-Vertrag ohne Zusatzfelder validieren |
0 |
Für ein Deployment, das unter einem Präfix wie /laya veröffentlicht wird, setze LAYA_ROOT_PATH=/laya.
FastAPI nutzt es beim Generieren der OpenAPI- und Swagger-UI-URLs. Konfiguriere den Reverse-Proxy so, dass er
/laya entfernt, bevor er Anfragen an Laya weiterleitet; die Routen der App bleiben intern /health und
/v1/systemone.
Für Container, einschließlich CUDA- und ARM64-Images, siehe Docker-Schnellstart.
Für stoßweisen lokalen Einsatz setze LAYA_IDLE_UNLOAD_SECONDS=300. Inferenz und Entladen laufen auf demselben Worker, und das Idle-Fenster beginnt erneut, wenn ein einzelner oder Batch-Forward-Pass endet, auch bei fehlgeschlagenen Anfragen. Die nächste Vorhersage zahlt einen Cold Load. Das Entladen gibt Modellreferenzen und Gerätecaches frei, einschließlich Metal; der Prozess-Allocator kann RAM-Seiten behalten, sodass der Prozess-RSS nicht um die Größe des Checkpoints fallen muss.
Endpunkte
GET /health
Immer offen (keine Authentifizierung), und bleibt während der Inferenz ansprechbar, weil der CPU-gebundene
Forward-Pass in seinem eigenen Worker läuft, nicht in der Event-Loop. Die Felder unterhalb von liveness
sind auf einem Deployment, das LAYA_API_KEY gesetzt hat, nicht offen: ohne den Bearer antwortet
/health nur mit {"status": "ok"} und sonst nichts, weil der Rest residente Checkpoints, ihre
genauen Revisions-SHAs, den Gerätezustand und den letzten Fallback-Grund jedes Checkpoints nennt, was
Host-Hardware zitiert. Eine Sonde braucht nur die 200, also ist ein Healthcheck nicht betroffen und ein
falscher Bearer ist weiterhin eine 200 statt einer 401. Ohne gesetztes LAYA_API_KEY bekommt jeder
Aufrufer die hier gezeigte vollständige Nutzlast.
{"status": "ok", "loaded": ["english", "multilingual"], "revisions": {"english": "...", "multilingual": "..."},
"device": "cuda", "device_is_preference": false,
"checkpoint_devices": {"english": "cuda", "multilingual": "cuda"},
"cpu_fallbacks": {"english": {"count": 0, "last_reason": null}, "multilingual": {"count": 0, "last_reason": null}}}
Die Antwort eines Servers, sodass die Blöcke zueinander passen: jeder Schlüssel von revisions,
checkpoint_devices und cpu_fallbacks ist ein Name in loaded. tests/test_serve.py hält dieses
Beispiel Feld für Feld gegen den Handler, der es erzeugt.
Bei aktiviertem Idle-Entladen enthalten authentifizierte Health-Antworten außerdem idle_unload_seconds
(das konfigurierte Fenster) und idle_seconds (Zeit seit der letzten Inferenzanfrage oder deren
Abschluss). Health-Sonden setzen diese Uhr nicht zurück. Eine leere loaded-Liste ist nach einem
Idle-Entladen normal.
statusistok, wann immer der Prozess überhaupt antwortet. Über die Checkpoints sagt es nichts.loadedlistet die im Speicher residenten Checkpoints. Es ist leer, bis eine Anfrage einen baut, wasLAYA_PRELOAD=0den Prozess tun lässt.revisionsist die Artefakt-Revision, aus der jeder residente Checkpoint geladen wurde, mit denselben Namen wieloadedals Schlüssel, sodass ein Deployment bestätigen kann, was es tatsächlich ausliefert.deviceist das Gerät, auf dem ein residenter Checkpoint wirklich rechnet, was nicht immer das ist, wasLAYA_DEVICEverlangt hat: ein Checkpoint, der eine GPU will, die er nicht bekommen kann, fällt still auf CPU zurück und antwortet trotzdem korrekt. Ohne residenten Checkpoint ist es stattdessen die konfigurierte Präferenz.device_is_preferenceisttruegenau solange nichts resident ist, undfalse, sobald der Handler messen kann. Das ist der Unterschied zwischen einem Server, der seine Konfiguration meldet, und einem, der meldet, wo seine Arbeit stattfindet: einer, der seine GPU still verloren hat, sagtfalsemitdevicecpu, statt weitercudazu antworten.checkpoint_devicesgibt die Messung pro Checkpoint, mit den Namen inloadedals Schlüssel;deviceist der erste dieser Werte.cpu_fallbackszählt pro residentem Checkpoint die Anfragen, die den GPU-Speicher erschöpft haben und einmal auf CPU wiederholt wurden:countseit Prozessstart undlast_reasonmit dem Fehlertext der letzten. Die Herabstufung ist auf die fehlgeschlagene Anfrage beschränkt, sodass ein Checkpoint, der auf CPU gebaut wurde, weil die GPU nie verfügbar war, kein Fallback ist und hier0zählt – das zeigt sich indevice.
POST /v1/systemone
Eine Anfrage trägt einen state und beliebig viele Fragen darüber:
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
"state": "I was charged twice this month, I want my money back",
"questions": {
"queue": {"type": "choice", "instructions": "Which team?",
"criteria": {"billing": "billing and refunds", "tech": "login and app issues",
"other": "everything else"}},
"urgency": {"type": "score", "instructions": "How urgent?",
"criteria": ["calm", "firm", "angry", "furious"]}
}
}'
| Feld | erforderlich | Bedeutung |
|---|---|---|
state |
ja | Text, E-Mail, Ticket oder JSON-Dokument, über das entschieden wird; ein fehlender oder auf null gesetzter Zustand ist ein 400 |
questions |
ja | Objekt mit Frage-IDs als Schlüssel; jede Frage ist choice / score / noul mit instructions und criteria |
model |
nein | benennt einen Checkpoint; ein Pfad oder eine nicht veröffentlichte Hub-ID ist ein 422, alles andere wird ignoriert (siehe unten) |
task |
nein | erzwingt einen Checkpoint über den Workflow-Namen, statt das Routing entscheiden zu lassen; ein unbekannter Name ist ein 422, der ihn benennt |
lang |
nein | ein Sprachcode (de, en-US), der die Erkennung überspringt, wenn er eine Sprache benennt; ein leerer oder nicht erkannter Code fällt auf die Erkennung zurück |
lang_guess |
nein | ein Sprachcode aus dem eigenen Identifikator des Clients, der nach lang und vor der Erkennung konsultiert wird; jeder nicht englische Code routet zum mehrsprachigen Checkpoint |
max_len |
nein | gesamtes Token-Fenster für diese Anfrage, begrenzt durch LAYA_MAX_TOKEN_BUDGET |
head_max_len |
nein | Token-Fenster, das sich der Options-Prompt teilt, dieselbe Obergrenze; siehe Das Token-Budget erweitern, wann eine Frage es braucht |
min_confidence |
nein | Enthaltungsschwellenwert in [0.0, 1.0]; eine Antwort, deren answer_confidence darunter fällt, kommt als low_confidence markiert zurück, und die Antwort selbst bleibt erhalten |
model, task, lang, lang_guess, max_len, head_max_len und min_confidence sind die Argumente,
die Router.predict annimmt und die ein JSON-Body angeben kann; jedes wird nur weitergeleitet, wenn die Anfrage
es sendet, sodass ein fehlendes die eigene Router(...)-Einstellung des Deployments zuständig lässt. Die fünf
Hook-Argumente, die predict ebenfalls annimmt – hooks, on_predict_start, on_predict_end,
hooks_raise, hooks_timeout – werden mit einem 422 abgelehnt statt verworfen: Ein Hook ist ein Callable,
das innerhalb des Serverprozesses läuft, und die letzten beiden sagen, wie die Hooks ausgeführt werden, die ein
Deployment installiert hat, sodass kein Wert, den ein Aufrufer sendet, hier eine Bedeutung hat. Dieselben fünf
werden clientseitig von einem LangChain-Knoten mit einer base_url (laya.integrations.langchain) abgelehnt,
sodass eine Kette und ein roher HTTP-Client jetzt dieselbe Antwort bekommen.
model wird akzeptiert, damit ein Jev-Client weiterhin eines senden kann. Die öffentlichen Hugging-Face-IDs
(convaiinnovations/laya-multilingual, convaiinnovations/laya-typed-decisions), die Checkpoint-Namen
(english, multilingual, typed-decisions) und ihre Aliase wählen einen Checkpoint.
convaiinnovations/laya und jeder andere Wert, der kein Pfad und keine Hub-Repo-ID ist – einschließlich
einer Jev-ID wie jev-1 – bedeutet „lass den Router wählen”, und der routing-Block der Antwort
zeichnet auf, was gewählt wurde und warum. Ein Wert, der wie ein Dateisystempfad oder eine nicht
veröffentlichte Hub-ID aussieht (/path/to/checkpoint, org/repo, ~/ckpt, .\ckpt), ist ein 422
auf sowohl /v1/systemone als auch /v1/systemone/batch: Dieser Server kann ihn nicht laden, und mit
einem anderen Checkpoint zu antworten würde das verbergen. Das detail ist derselbe unknown model-Text,
den core auslöst, plus der Hinweis, model wegzulassen, damit der Router wählt.
Antwort
{
"model": "laya-rl-agent",
"answers": {
"queue": {"type": "choice", "choice": "billing",
"probabilities": {"billing": 0.9519, "tech": 0.0327, "other": 0.0154},
"confidence": 0.797, "answer_confidence": 0.9519,
"action": {"act_probability": 1.0}},
"urgency": {"type": "score", "score": 1.6994,
"legend": {"0": "calm", "1": "firm", "2": "angry", "3": "furious"},
"probabilities": {"0": 0.0249, "1": 0.4136, "2": 0.3985, "3": 0.1629},
"confidence": 0.1925, "answer_confidence": 0.4136,
"action": {"act_probability": 1.0}}
},
"usage": {"input_tokens": 83, "output_tokens": 0, "state_tokens": 12,
"state_tokens_dropped": 0, "truncated": false, "truncated_questions": []},
"routing": {"model": "english", "repo": "convaiinnovations/laya", "reason": "English Latin text",
"detection": {"script": "latin", "script_profile": {"latin": 1.0}, "language": "en",
"is_english": true, "language_undecided": false, "diacritic_rate": 0.0,
"non_latin_fraction": 0.0, "mixed_segment": null},
"workflow": null}
}
Das Beispiel ist eine Antwort, die dieser Server gegeben hat, wörtlich: die obige Anfrage, der
zwischengespeicherte english-Checkpoint auf CPU. answers und usage sind die Schlüssel, die
Jev-Clients dekodieren; model ist der konstante Name des Entscheidungskopfs, und der Checkpoint, der
geantwortet hat, steht in routing.
| Antworttyp | Schlüssel |
|---|---|
choice |
choice (die Argmax-Option), probabilities pro Option |
score |
score (erwarteter Stufenindex, kann zwischen Stufen fallen), probabilities mit Schlüsseln "0".. "k-1", legend, das Index auf den Stufentext abbildet |
noul |
noul, die Wahrscheinlichkeit der Ja-Option |
| alle | confidence, answer_confidence und action.act_probability |
| gate | abstention, abstention_threshold und low_confidence, geschrieben vom Enthaltungs-Gate – siehe unten |
Die Gate-Zeile ist der Enthaltungsbericht (#361) und die einzige Möglichkeit, wie ein Aufrufer sehen
kann, dass das Gate, für das er bezahlt hat, gelaufen ist. Eine Anfrage, die min_confidence setzt,
bekommt ihn; eine, die das nicht tut, bekommt keinen der drei Schlüssel. abstention ist einer von drei
Zuständen und wird auf jede Antwort einer gegateten Anfrage geschrieben: passed (seine Konfidenz
hat den Schwellenwert überschritten), abstained (sie fiel darunter, und low_confidence ist true
genau auf diesen Antworten) oder unevaluated (die Antwort trug keine brauchbare Konfidenz, sodass das
Gate nicht entscheiden konnte – das als Bestehen zu melden wäre dieselbe Lüge wie das Melden als Flag).
abstention_threshold gibt den Schwellenwert zurück, gegen den diese Zustände gemessen wurden, was
einen Batch-Lauf mit Schwellenwerten pro Klasse nachträglich wieder aufteilbar macht. Ohne gesetztes
min_confidence erscheint keiner der drei Schlüssel auf irgendeiner Antwort: die Abwesenheit ist der
Bericht, kein vierter Zustand, und so unterscheidet ein Aufrufer einen ungegateten Lauf von einem
bestandenen Gate. min_confidence von genau 0.0 wurde gesetzt, also werden Zustände gemeldet, und
nichts kann darunter fallen, also liest jede Antwort passed – die zurückgegebene 0.0 ist das, was
das von einem Bestehen bei einem echten Schwellenwert unterscheidet. Die Antwort selbst bleibt in jedem
Zustand erhalten; das Gate markiert, es verwirft nicht.
usage berichtet, woraus der Forward-Pass gebaut wurde. Wie viel eines Zustands das Modell liest, ist
ein Token-Budget, keine Zeichenzahl, und das Budget bewegt sich mit max_len, head_max_len und dem
Options-Prompt jeder Frage selbst (#174), sodass diese Schlüssel der einzige Ort sind, an dem diese
Tatsache sichtbar ist:
usage-Schlüssel |
Bedeutung |
|---|---|
input_tokens |
Nicht-Pad-Token der Zeilen des Zustands – eine Zeile pro Frage, sodass es mit den Fragen wächst, statt eine Kontextlänge zu sein |
output_tokens |
immer 0 – der Kopf antwortet in einem Durchgang, er generiert nichts |
state_tokens |
Token, die der gesamte serialisierte Zustand braucht |
state_tokens_dropped |
Token davon, die mindestens eine Frage nicht bekam: der schlimmste Fall über die Fragen, da jede dem Zustand einen anderen Raum lässt |
truncated |
true, wenn dieser schlimmste Fall etwas verworfen hat |
truncated_questions |
die IDs der Fragen, deren eigenes Fenster beschnitten wurde, [] wenn keine |
options |
nur vorhanden, wenn einige Fragenoptionen nicht mehr je eine Token-Spanne haben: mit der Frage-ID als Schlüssel, mit total (den Optionen, die diese Frage definiert), distinct (den Spannen, die die Sequenz erreicht haben) und tokens_per_option |
Eine abgeschnittene Antwort ist immer noch eine Antwort – der Kopf entscheidet über die ihm gegebene Evidenz –, aber ein Aufrufer, der Zustände nach Zeichenzahl bemisst, kann den Schnitt nirgendwo sonst in der Antwort sehen.
routing zeichnet auf, welcher Checkpoint geantwortet hat und warum:
routing-Schlüssel |
Bedeutung |
|---|---|
model |
der Checkpoint, der geantwortet hat: english, multilingual oder typed-decisions |
repo |
seine öffentliche Hugging-Face-ID |
reason |
der Satz für die Wahl, der die Evidenz nennt, auf die er gehandelt hat |
detection |
laya.lang.analyse() auf dem Zustand – script, script_profile, language, is_english, language_undecided, diacritic_rate, non_latin_fraction, mixed_segment – oder null, wenn die Route entschieden hat, bevor sie den Text las |
workflow |
der typed-decisions-Workflow, zu dem die Frage-IDs passen, oder null |
detection ist null auf jedem Pfad, der entscheidet, ohne den Zustand zu lesen: einer, der durch
model oder task erzwungen wurde, einer, der durch lang oder lang_guess beantwortet wurde, oder
einer, der aus den Frage-IDs einem typed-decisions-Workflow entsprach. Ein lang_guess hinterlässt
keinen eigenen Schlüssel – der Hinweis, auf den er gehandelt hat, ist in reason genannt. Die Zweige
model und task melden workflow ebenfalls als null, weil sie antworten, bevor die Frage-IDs
gelesen werden.
Konfidenz: zwei Zahlen, nicht austauschbar
answer_confidenceist die Wahrscheinlichkeitsmasse auf der gemeldeten Antwort (max(p)). Es ist die Größe, die das Temperatur-Scaling anpasst, und diejenige, auf der die ECE-Zahlen dieses Repos berechnet werden, sodass sie die Gating-Eigenschaft trägt, auf die sich die Seite Benchmarks und bekannte Grenzen stützt – aber nur für einen Checkpoint, dessen Temperatur-Anpassung an deinem Verkehr validiert wurde.confidencebedeutet je Typ etwas anderes: normalisierte Entropie1 - H(p)/log(k)beichoiceundscoreundmax(p_yes, p_no)beinoul(wo sie gleichanswer_confidenceist).
Vergleiche die beiden niemals gegen einen einzigen Schwellenwert. Beachte auch den Unterschied beim Portieren
von Jev: TypeSafe definiert Konfidenz als (n*p_max - 1)/(n - 1), sodass ein aus einem Jev-Deployment
übernommener Schwellenwert bei Layas Entropiewert anders gatet.
Strikter Jev-Vertrag: LAYA_JEV_STRICT
Die obige Nutzlast ist die vollständige Laya-Nutzlast. Der Jev-Vertrag, an den ein Client sie halten
mag, definiert weniger: drei Top-Level-Felder (model, answers, usage), die vertraglichen
Schlüssel auf jeder Antwort und sonst nichts, und ein usage aus den zwei Token-Zahlen. Ein Client,
der die Antwort gegen diesen Vertrag ohne Zusatzfelder validiert – OpenClaws TypeSafe-Provider-Plugin
ist einer – lehnt die vollständige Nutzlast ab, also projiziert LAYA_JEV_STRICT=1 die Antwort vor dem
Antworten auf den Vertrag, auf sowohl /v1/systemone als auch /v1/systemone/batch:
- der Root behält nur
model,answersundusage;routingwird nicht gesendet; - eine
choice-Antwort behältchoice,probabilitiesundconfidence; - eine
score-Antwort behältscore,probabilities,confidenceundlegend; - eine
noul-Antwort behält nurnoul; usagebehältinput_tokensundoutput_tokens; die Truncation-Fakten und die Obergrenze der zusammengefassten Optionen werden nicht gesendet.
Die Projektion behält nur die vertraglichen Schlüssel und berechnet nichts neu: jeder Wert ist der, den
das Ergebnis bereits trägt, sodass die Wahrscheinlichkeiten und Scores, die ein strikter Client liest,
identisch mit denen sind, die die vollständige Nutzlast meldet. Der Standard bleibt die vollständige
Nutzlast, und ein Deployment, das das Flag einschaltet, verliert die Truncation-Sichtbarkeit, die
usage bietet – ein beschnittener Zustand ist dann in den Logs sichtbar, nicht in der Antwort. Score
criteria sollte unter dem strikten Vertrag einfache Strings bleiben: ein strikter Client vergleicht
das zurückgegebene legend mit den Kriterien, die er gesendet hat, und Laya rendert ein strukturiertes
Kriterium mit Pythons JSON, was ein JavaScript-Aufrufer, der seine eigenen Kriterien stringifiziert,
nicht Byte für Byte treffen muss.
Erfolgreiche Antworten tragen außerdem Server-Timing: inference;dur=<ms> und X-Inference-Time-Ms.
Grenzen
Anfrage-Leitplanken werden vor der Tokenisierung geprüft, sodass eine übergroße Anfrage den Server nichts außer
den gelesenen Bytes kostet. Jede von ihnen ist ein 413; das detail sagt, welches Limit erreicht wurde.
| Limit | Wert |
|---|---|
| Anfrage-Body | 2 MiB, während des Streamings erzwungen – ein gechunkter oder zu niedrig angegebener Content-Length kann es nicht umgehen |
state |
50.000 Zeichen des Textes, den das Modell erhält – der String selbst bei einem String-Zustand, json.dumps(state, ensure_ascii=False) bei einem Objekt oder Array |
| Fragen pro Anfrage | 64 |
states pro Batch-Anfrage |
64 |
Optionen pro choice-Frage |
100 |
Stufen pro score-Frage |
32 |
| Optionen über alle Fragen hinweg | 512 |
| gleichzeitig zugelassene Anfragen | LAYA_MAX_CONCURRENT (16) |
/v1/systemone/batch ist anders begrenzt, und nicht durch eine Ablehnung. Es tokenisiert jeden Zustand
einmal pro Frage und fasst jede Zeile zu einem einzigen Tensor zusammen, sodass sich die Feldgrenzen
multiplizieren: 64 Zustände mit 64 Fragen sind 4096 Zeilen, was jede andere Grenze auf dieser Seite
erlaubt. Was eine Zeile kostet, ist ihre Breite, und max_len ist selbst ein Anfragefeld, sodass die
Kosten eines Batches states x questions x width sind.
Statt einen großen Batch abzulehnen, teilt der Endpunkt ihn: Wenn dieses Produkt
LAYA_MAX_BATCH_TOKENS (standardmäßig 131.072) überschreitet, wählt er eine batch_size, sodass jeder
Forward-Pass innerhalb des Budgets bleibt, und Router.predict_batch führt den Batch in mehreren
Pässen aus. Jeder Zustand wird weiterhin beantwortet und die Antwort bleibt unverändert. Eine Anfrage,
deren Zeilen bereits passen, bekommt überhaupt keine batch_size übergeben, verhält sich also genau
wie zuvor – was wichtig ist, weil die Batch-Form Fließkommaergebnisse verschieben kann. Eine
batch_size, die der Aufrufer sendet, gewinnt immer: er hat eine Form verlangt.
Standardmäßig gehen 256 Zeilen in einem Pass durch – 64 Zustände mit 4 Fragen oder 8 mit 32. Größere
Batches werden geteilt, und ein höheres max_len macht jeden Pass schmaler, statt das 16-Fache an
Arbeit zu kosten. Was das nicht begrenzt, ist, wie lange eine Anfrage den Server belegt; das sind
LAYA_MAX_CONCURRENT und der einzelne Inferenz-Worker, und das gilt bereits für eine
/v1/systemone-Anfrage über einen Zustand mit 50.000 Zeichen.
Die Options-Obergrenzen sind reine HTTP-Schutzmechanismen gegen Amplifikation; das Modell selbst passt
Optionstoken in ein Fenster head_max_len=192 ein, sodass eine Frage innerhalb der HTTP-Grenzen trotzdem als
422 abgelehnt werden kann, wenn die Optionstexte zusammen dieses Budget überschreiten. Das
Evaluierungs-Harness führt dieselben Anfragen in-process ohne die HTTP-Schicht aus.
Fehler
| Status | wann | detail des Bodys |
|---|---|---|
400 |
der Body ist kein gültiges JSON, kein Objekt, hat keine questions, state fehlt oder ist null, questions ist kein Objekt, oder ein String irgendwo im Body enthält ein ungepaartes \udXXX-Surrogat-Escape |
was falsch ist |
401 |
LAYA_API_KEY ist gesetzt und der Bearer-Token fehlt oder ist falsch |
invalid or missing bearer token |
413 |
irgendein Limit von oben | welches Limit und um wie viel |
422 |
die Frage ist wohlgeformtes JSON, aber für Laya ungültig (unbekannter Typ, Optionen über dem Kopf-Budget), oder eine Anfrage-Steuerung (lang, min_confidence, ein Hook-Argument) hat nicht die Form, die dieser Endpunkt akzeptiert |
benennt die Frage oder das Feld und was zu korrigieren ist |
500 |
die Inferenz ist aus einem anderen Grund fehlgeschlagen | inference failed – immer dieser String, damit Pfade, Gewichte und Speicherzustand nie preisgegeben werden; die Ursache steht im Server-Log |
503 |
bereits LAYA_MAX_CONCURRENT Anfragen sind in Bearbeitung |
server busy, try again later |
Der 400 für ungepaarte Surrogate ist der, der ungewöhnlich aussieht. \udXXX ohne Paar ist
gültiges JSON, aber das Zeichen, das es nennt, kann nicht UTF-8-kodiert werden, sodass der Tokenizer
einen TypeError auslöst – der eigene String des Aufrufers, der als Serverfehler ankommt, mit einem
Traceback pro Anfrage. Beide Entscheidungsrouten durchlaufen daher den geparsten Body nach einzelnen
Surrogaten und lehnen eines ab, bevor es die Inferenz erreicht. Der Durchlauf läuft nach den
Größenprüfungen, sodass ein übergroßer Body weiterhin zuerst abgelehnt wird und die Zeichen- und
Fragegrenzen begrenzen, was er erreichen kann. Ein gepaartes Surrogat ist, wenn der Parser fertig
ist, ein gewöhnliches Astralzeichen, sodass ein Emoji in einem Zustand nicht betroffen ist.
Über der Obergrenze liegende Last wird abgelehnt, nicht in eine Warteschlange gestellt: Clients, die einen
Zulassungsplatz halten, während sie einen langsamen Body streamen, können /health nicht aushungern, und ein
erneuter Versuch kann den Platz einnehmen, den ein abgelehnter Client hinterlassen hat.
Nebenläufigkeitsmodell
Die Inferenz ist ein synchroner torch-Aufruf, der auf der CPU Hunderte von Millisekunden bis Sekunden dauert,
sodass sie nie in der Event-Loop läuft: Anfragen werden an einen Executor mit einem einzigen Worker übergeben,
was einen Forward-Pass auf einmal bedeutet – die Form, die ein einzelner Checkpoint auf einem Gerät will. Die
Zulassung (der Semaphor LAYA_MAX_CONCURRENT) wird geprüft, bevor ein Body-Byte gelesen wird, und über die
Inferenz hinweg gehalten; das Inferenz-Gate wird erst betreten, nachdem der Body vollständig ist, sodass ein
langsamer Client einen Zulassungsplatz hält, aber nie einen Inferenzplatz.
(Noch) nicht vorhanden
Dieser Server spricht bewusst nur ein Protokoll. Es gibt keinen OpenAI-kompatiblen Endpunkt; führe
stattdessen mehrere Fragen in einer Anfrage aus, da sie sich einen einzigen Forward-Pass pro Fragenset
teilen. Die eine andere Route ist POST /v1/systemone/batch, die ein questions-Set über ein Array
von states beantwortet. Sie hat noch keinen Abschnitt auf dieser Seite – ihre Anfrageform steht im
Self-Hosting-Abschnitt des README – und jede Prüfung oben gilt für sie wie für POST /v1/systemone:
dieselben Form-400s, dieselbe Unpaired-Surrogate-Ablehnung, dieselbe Auth, Admission, Größenlimits,
Body-Control-Validierung und 500-Zuordnung. Die laya-CLI und der MCP-Server decken die lokale
Nutzung ab – siehe das
README.