API-Referenz
Alles auf dieser Seite lässt sich aus laya (die gängigen Namen) oder aus laya.hooks (die
gesamte Oberfläche) importieren.
from laya import PredictContext, PredictHook, Hook
from laya.hooks import HOOK_EVENTS, normalise_hooks, dispatch, aggregate_usage
- PredictContext
- Hook-Protokoll
- Komforttypen
- Konfigurationsoberfläche
- Ereignis-Payloads
- Validierung
- Erweiterte Hilfsfunktionen
PredictContext
Ein PredictContext wird einmal pro öffentlichem Aufruf erzeugt und an jeden Hook dieses Aufrufs
übergeben. Router.predict_batch ist die einzige Ausnahme: es erzeugt einen pro Anfrage, so wie es
ein Router.predict-Aufruf für diese Anfrage täte, sodass seine Router-Hooks einmal pro Anfrage mit
je einem Zustand ausgelöst werden. Es ist veränderbar: Hooks dürfen states, questions und results umschreiben, und on_route darf
decision umschreiben. Es verwendet Identitätsgleichheit (eq=False), sodass ein Kontext hashbar
ist und zwei Kontexte nie gleich sind.
@dataclass(eq=False)
class PredictContext:
states: List[Any]
questions: Dict[str, Any]
run_id: str = <uuid4 hex>
results: Optional[List[Dict[str, Any]]] = None
decision: Optional[Dict[str, Any]] = None
model: Optional[str] = None
agent: Any = None
router: Any = None
max_len: Optional[int] = None
head_max_len: Optional[int] = None
usage: Optional[Dict[str, int]] = None
started_at: float = <perf_counter()>
elapsed_ms: Optional[float] = None
error: Optional[BaseException] = None
| Feld | Typ | gesetzt, wenn | veränderbar | Bedeutung |
|---|---|---|---|---|
states |
list |
immer | ja (Start) | die Zustände für diesen Aufruf. system_one/Router.predict übergeben einen; Agent.predict_batch übergibt viele; Router.predict_batch übergibt einen pro Anfrage. Ein Start-Hook darf die Liste ersetzen. |
questions |
dict |
immer | ja (Start) | die Fragen. Ein Start-Hook darf das dict ersetzen. |
run_id |
str |
immer | nein | eine eindeutige ID, die von jedem Hook dieses Aufrufs geteilt wird. Nutze sie, um Ereignisse und Spans zu korrelieren. |
results |
list | None |
Ende (und bei einem Skip) | ja (Ende) | Ergebnis-dicts pro Zustand, jedes geformt wie der Rückgabewert von system_one. None, bis die Inferenz fertig ist. |
decision |
dict | None |
nur Router | ja (route) | die RouteDecision (ein dict), die den Checkpoint ausgewählt hat. |
model |
str | None |
immer | nein | die Checkpoint-ID: Agent.model_id für einen Agent, das aufgelöste Alias (zum Beispiel "english") für einen Router. |
agent |
Agent | ONNXAgent | None |
predict-Ereignisse | nein | die Laufzeit, die den Aufruf beantwortet. |
router |
Router | None |
Router-Ereignisse | nein | der Router, wenn einer im Spiel ist. |
max_len |
int | None |
immer | ja (Start) | Token-Budget pro Aufruf für den Encoder. None verwendet die Agent-Konfiguration. |
head_max_len |
int | None |
immer | ja (Start) | Token-Budget pro Aufruf für den Frage-Head. None verwendet die Agent-Konfiguration. |
usage |
dict | None |
Ende | ja (Ende) | {"input_tokens", "output_tokens"}, summiert über die Zustände des Aufrufs. |
started_at |
float |
immer | nein | time.perf_counter(), als der Aufruf begann. |
elapsed_ms |
float | None |
Ende | nein | Wanduhrzeit für den gesamten Aufruf, in Millisekunden. |
error |
BaseException | None |
Fehlerpfad | nein | die Ausnahme, gesetzt vor on_error und on_predict_end. |
PredictContext.skip(results)
Kürzt die Inferenz ab. Aus on_predict_start aufgerufen setzt es ctx.results, sodass der
Vorwärtsdurchlauf übersprungen wird; on_predict_end läuft trotzdem und die übergebenen Ergebnisse
werden zurückgegeben.
Der Schlüssel muss alles abdecken, wovon die Antwort abhängt, und der Hook muss alles abdecken, was
der Aufruf trägt: Hooks werden einmal pro Aufruf ausgelöst, und predict_batch ruft sie mit allen
Zuständen auf einmal auf.
CACHE = {}
def key(ctx, index):
# Not sort_keys=True: criteria order is positional, so two orders are two questions,
# and the checkpoint and token budget change the answer too.
payload = json.dumps([ctx.states[index], ctx.questions, ctx.model,
ctx.max_len, ctx.head_max_len], default=str)
return hashlib.sha256(payload.encode()).hexdigest()
def cache_read(ctx):
hits = [CACHE.get(key(ctx, i)) for i in range(len(ctx.states))]
if all(hit is not None for hit in hits):
ctx.skip(hits) # one entry per state, same shape as predict_batch's return
def cache_write(ctx):
for i, result in enumerate(ctx.results or []):
CACHE[key(ctx, i)] = result
laya.load("convaiinnovations/laya", on_predict_start=cache_read, on_predict_end=cache_write)
tests/test_hooks_api.py führt diesen Block aus, ebenso examples/hooks/cache.py und die
Cache-Blöcke von docs/hooks/patterns.md und docs/hooks/examples.md, und prüft alle vier
Schlüssel auf dieselbe Weise, sodass eine Seite keinen Schlüssel lehren kann, von dem das Beispiel
bereits abgewichen ist.
Beim Router bekommt eine übersprungene Payload einen routing-Schlüssel hinzugefügt (ohne einen
bereits vorhandenen zu überschreiben), sodass Router.predict seine dokumentierte Rückgabeform
behält.
Hook-Protokoll
Hook ist ein typing.Protocol. Implementiere jede Teilmenge der Methoden; der Rest wird
übersprungen.
class Hook(Protocol):
def on_predict_start(self, ctx: PredictContext) -> None: ...
def on_predict_end(self, ctx: PredictContext) -> None: ...
def on_route(self, ctx: PredictContext) -> None: ...
def on_load(self, ctx: PredictContext) -> None: ...
def on_evict(self, ctx: PredictContext) -> None: ...
def on_error(self, ctx: PredictContext) -> None: ...
| Ereignis | wo | läuft | kann ändern |
|---|---|---|---|
on_predict_start |
Agent, Router | vor der Tokenisierung/dem Vorwärtsdurchlauf | states, questions oder skip() |
on_predict_end |
Agent, Router | nachdem Ergebnisse existieren, bei Erfolg oder Fehler | results |
on_route |
Router | nach der Erkennung, vor dem Laden | decision |
on_load |
Router | nachdem ein Checkpoint gebaut wurde | nichts (beobachten) |
on_evict |
Router | nachdem ein Checkpoint freigegeben wurde | nichts (beobachten) |
on_error |
Agent, Router | wenn ein predict-Aufruf fehlschlägt | nichts (beobachten) |
Ein Hook darf zusätzliche Attribute und Methoden definieren; nur die sechs Ereignisnamen werden herangezogen. Wenn ein Hook einen der sechs als Nicht-Callable definiert, schlägt die Konfiguration sofort fehl (siehe Validierung).
BaseHook
BaseHook ist das konkrete Gegenstück zum Protokoll: eine Klasse mit einem No-op-Rumpf für jedes
Ereignis. Leite davon ab und überschreibe nur die Ereignisse, die du brauchst.
from laya import BaseHook
class Audit(BaseHook):
def on_predict_end(self, ctx):
ship(ctx.run_id, ctx.results)
Hook ist am besten, wenn du strukturelles Typing willst (jedes Objekt mit den richtigen Methoden);
BaseHook ist am besten, wenn du eine explizite Basis willst, von der du ableitest und auf der du
super() aufrufst.
Komforttypen
PredictHook = Callable[[PredictContext], None]
PredictHook ist der Typ eines einfachen Callables, das mit on_predict_start= / on_predict_end=
verwendet wird. Übergib ein einzelnes Callable oder eine Sequenz davon; jedes wird in einen
minimalen Hook verpackt.
Registrierung zur Laufzeit
Jede Laufzeit mischt HookRegistry ein, sodass Hooks nach der Konstruktion hinzugefügt, entfernt
oder begrenzt werden können. Mutation ist thread-sicher; ein Aufruf liest eine Momentaufnahme der
Liste, sodass das Hinzufügen oder Entfernen eines Hooks einen laufenden Aufruf nie stört.
agent.add_hook(tracer) # one hook or a sequence; returns self for chaining
agent.remove_hook(tracer) # by identity; True if it was installed
with agent.hooks_installed(debug): # installed for the block, removed on exit
agent.system_one(state, questions)
add_hook akzeptiert dieselben Objekte wie hooks= (keine einfachen Callables). hooks_installed
nimmt beliebig viele Hook-Objekte oder -Sequenzen und stellt beim Verlassen die vorherige Liste
wieder her, auch wenn der Block eine Ausnahme auslöst.
Prozessweite Standardwerte
laya.hooks führt eine kleine prozessweite Registry, sodass ein Tracer, ein Metrik-Hook oder ein
Tenant-Tagger nicht durch jeden Agent und Router geschleust werden muss. Standardwerte laufen
zuerst, dann die auf der Instanz installierten Hooks, dann die Hooks pro Aufruf.
from laya import hooks
hooks.set_default_hooks(hooks=[Tracer()]) # replaces the set, accepts the hooks= arguments
hooks.add_default_hook(Metrics()) # appends
hooks.clear_default_hooks() # removes everything
hooks.default_hooks() # a copy of the current list
hooks.compose_hooks(agent.hooks) # defaults + installed (advanced)
Standardwerte gelten für jedes Ereignis, einschließlich der Router-Lebenszyklusereignisse on_load
und on_evict. Die Registry wird zur Aufrufzeit gelesen, sodass Hooks, die nach dem Bau eines
Agent oder Router gesetzt werden, weiterhin gelten. Es gibt keinen Opt-out pro Instanz; rufe
clear_default_hooks() auf, um den prozessweiten Satz abzuschalten.
Asynchrone Hooks
Ein Ereignis kann eine Koroutine sein. Verpacke den Hook in AsyncHook, und seine async def-Methoden
laufen im synchronen Kern bis zum Ende durch:
from laya import AsyncHook
class Remote:
async def on_predict_end(self, ctx):
await ship(ctx.results)
agent = laya.load("convaiinnovations/laya", hooks=[AsyncHook(Remote())])
Ein einfaches asynchrones Callable, das an on_predict_start= / on_predict_end= übergeben wird,
funktioniert ebenfalls, weil dispatch jedes Awaitable ausführt, das ein Hook zurückgibt.
Wo die Koroutine läuft:
- Wenn der aufrufende Thread keine laufende Schleife hat, wird sie mit
asyncio.runausgeführt. - Wenn er bereits eine hat (ein Aufrufer innerhalb einer asynchronen Funktion), läuft sie auf einer
dedizierten Hintergrundschleife, sodass der aufrufende Thread blockieren kann, ohne in einen
Deadlock zu geraten. Übergib
AsyncHook(hook, loop=...), um auf eine bestimmte Schleife zu leiten; sie muss laufen und darf nicht die eigene Schleife des aufrufenden Threads sein. Beides wird geprüft: eine gestoppte Schleife und die eigene Schleife des Aufrufers lösen jeweilsValueErroraus, statt für immer zu blockieren.
Ein Hook ohne async-Methoden ist nicht betroffen.
Konfigurationsoberfläche
Jeder Einstiegspunkt akzeptiert dieselben Hook-Parameter. hooks nimmt ein Objekt oder eine Sequenz
von Objekten; on_predict_start / on_predict_end nehmen ein Callable oder eine Sequenz.
| Parameter | Typ | Standard | Bedeutung |
|---|---|---|---|
hooks |
Hook | Sequence[Hook] | None |
None |
Lebenszyklus-Hooks (jedes der sechs Ereignisse). |
on_predict_start |
PredictHook | Sequence[PredictHook] | None |
None |
Komfort-Callables für ein Ereignis. |
on_predict_end |
PredictHook | Sequence[PredictHook] | None |
None |
Komfort-Callables für ein Ereignis. |
hooks_raise |
bool |
True |
True: eine Hook-Ausnahme wird weitergegeben. False: warnen und fortfahren. |
hooks_concurrent |
bool |
True |
False: Hooks unter einem Lock ausführen, einer nach dem anderen. |
hooks_timeout |
float | None |
None |
Zeitlimit pro Hook in Sekunden; None bedeutet kein Limit. |
Agent
Agent(
model_id_or_path="convaiinnovations/laya",
device=None, token=None, subfolder=None, fast=False, compile=False,
hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True, hooks_timeout=None,
)
load(..., hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True, hooks_timeout=None)
agent.predict_batch(states, questions, batch_size=None,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None, sort_by_length=False)
agent.system_one(state, questions,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None)
agent.predict_long(state, questions, window=None, stride=None, aggregate="auto",
batch_size=None, lang=None,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None)
agent.predict(...) # alias of system_one
-
hooks_raiseundhooks_timeoutauf einer Methode pro Aufruf haben standardmäßigNone, was „den Wert der Instanz verwenden“ bedeutet. -
hooks_concurrentgibt es nur auf Instanzebene. -
Bei
predict_longumschließen die Hooks die Inferenz, die den Zustand beantwortet; für ein Dokument, das mehrere Fenster braucht, ist das das eine gemeinsamepredict_batchüber sie:on_predict_startwird einmal ausgelöst, undctx.statesenthält die dekodierten Fenstertexte in Scan-Reihenfolge statt des Zustands des Aufrufers, der tokenisiert wurde, um sie zu erzeugen.statesist von einem Start-Hook aus veränderbar, sodass der Scan, der die Inferenz erreicht, nicht die vonpredict_longberechnete Aufteilung sein muss. Was sich ändert, ist das, was die Antwort behaupten kann:was der Start-Hook tat usage["windows"]answer["window"]antwortete mit ctx.skip([result])0fehlt – kein Fenster hat es bewertet ließ den Scan so, wie er gebaut wurde Nvorhanden – index,token_start/token_endbenennen den entscheidenden Spanersetzte den Scan, in welcher Form auch immer die Zustände, die bewertet wurden fehlt – die Offsets beschreiben die Fenster von predict_long, nicht den Text, der bewertet wurdeusage["windows"]ist über jeden Pfad hinweg die Summe, einschließlich des Pfads, auf dem ein Zustand bereits in ein einzelnes Fenster passte, sodass eine gecachte Antwort nie als ein Fenster gelesen wird, das das Modell gelesen hat.
Router
Router(
models=None, device=None, token=None, max_loaded=2, default="english",
auto_task_detection=False, standalone_repos=False, preload=False, lang_guess=None,
hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True,
)
router.route(state, questions=None, model=None, task=None, lang=None, lang_guess=None,
hooks=None, hooks_raise=None)
router.predict(state, questions, model=None, task=None, lang=None, lang_guess=None,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None)
router.predict_batch(requests, batch_size=None, hooks_timeout=None, min_confidence=None,
sort_by_length=False, hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=None)
router.system_one(...) # alias of predict
router.load(name) # builds on first use; fires on_load
router.preload(names=None) # builds several; fires on_load per build
router.unload(name=None) # frees one or all; fires on_evict
router.attach(name, agent) # registers an existing agent; does not fire on_load
router.loaded # list of resident checkpoint names
- Das
hooks=pro Aufruf beiroute,route_batch,predictundpredict_batchgilt für den gesamten Aufruf, einschließlichon_route. Beipredict_batchwird die Liste genauso zusammengesetzt wie beipredict(zuerst die installierten Hooks, dann die Liste pro Aufruf;Noneund[]fügen nichts hinzu) und läuft einmal pro Anfrage. route()ist öffentlich: ein Aufruf löston_routemit den installierten Hooks plus allenhookspro Aufruf aus.
ONNXAgent
ONNXAgent(model_id_or_path, onnx_path="laya.onnx", subfolder=None,
hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True)
onnx_agent.system_one(state, questions,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None)
onnx_agent.predict(...) # alias of system_one
ONNXAgent hat keinen Router, also exponiert es nur die Ereignisse auf predict-Ebene.
Ereignis-Payloads
Welche Felder gefüllt sind, pro Ereignis und Laufzeit:
| Ereignis | Laufzeit | states |
questions |
decision |
model |
agent |
router |
results |
usage |
elapsed_ms |
error |
|---|---|---|---|---|---|---|---|---|---|---|---|
on_predict_start |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | – | – | – | – |
on_predict_start |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – | – | – |
on_predict_end |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | ✓ | bei Erfolg | ✓ | bei Fehler |
on_predict_end |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | bei Erfolg | ✓ | bei Fehler |
on_error |
beide | ✓ | ✓ | ✓ (Router) | ✓ | ✓ | ✓ (Router) | – | – | – | ✓ |
on_route |
Router | ✓ | ✓ | ✓ | – | – | ✓ | – | – | – | – |
on_load |
Router | [] |
{} |
– | ✓ | ✓ | ✓ | – | – | – | – |
on_evict |
Router | [] |
{} |
– | ✓ | – | ✓ | – | – | – | – |
Timing-Details:
on_predict_endsiehtresultsauf dem Erfolgspfad. Auf dem Fehlerpfad istresultsNone, es sei denn, ein Start-Hook hat sie überskip()gesetzt, undusageist daher ebenfallsNone(es wird vonresultsabgeleitet);elapsed_msist immer gesetzt.on_errorläuft vor demfinally-Block, derelapsed_msundusageberechnet, sodass beide dortNonesind. Lies Timing und Nutzung stattdessen auson_predict_end.run_idist immer gefüllt.
Validierung
Die Konfiguration wird validiert, wenn Hooks normalisiert werden; das geschieht bei der Konstruktion
für installierte Hooks und zur Aufrufzeit für Hooks pro Aufruf. Folgendes löst TypeError aus:
| Fall | Meldung |
|---|---|
| eine Klasse wird statt einer Instanz übergeben | hooks entries must be instances, not classes; ... |
| ein Objekt implementiert keines der sechs Ereignisse | hooks entries must implement at least one of ... |
| ein Ereignisattribut ist nicht aufrufbar | hooks entry X.on_predict_start must be callable, got int |
on_predict_start= / on_predict_end= ist nicht aufrufbar |
on_predict_start must be callable, got int |
hooks= akzeptiert keine einfachen Callables, weil ein bloßes Callable nicht sagt, für welches
Ereignis es gedacht ist. Verwende dafür on_predict_start= / on_predict_end=.
Erweiterte Hilfsfunktionen
Sie werden intern verwendet und sind stabil, aber die meisten Nutzer brauchen sie nicht.
HOOK_EVENTS # tuple of the six event names, in dispatch order
normalise_hooks(hooks=None, on_predict_start=None, on_predict_end=None) -> list
dispatch(hooks, event, ctx, *, raise_errors=True, lock=None) -> None
aggregate_usage(results) -> {"input_tokens": int, "output_tokens": int}
dispatch(hooks, event, ctx, *, raise_errors=True, lock=None, timeout=None)
run_coroutine_sync(coro, loop=None)
normalise_hooks flacht ein hooks-Objekt/-Sequenz und die beiden Callables zu einer geordneten
Liste ab. dispatch ruft event auf jedem Hook auf, der es implementiert, wendet die
Raise-Richtlinie, das Lock und das Timeout an und führt das Ergebnis eines Hooks aus, wenn es
awaitable ist. run_coroutine_sync führt ein Awaitable aus synchronem Code bis zum Ende aus, auf der
Schleife des Aufrufers, wenn sie frei ist, oder auf einer Hintergrundschleife, wenn der Aufrufer
bereits eine hat. aggregate_usage summiert die Nutzungsblöcke pro Zustand.
from laya.hooks import normalise_hooks, dispatch, PredictContext
hooks = normalise_hooks(on_predict_start=[log, redact])
ctx = PredictContext(states=["..."], questions={...})
dispatch(hooks, "on_predict_start", ctx)
Siehe auch
- Lebenszyklus: wann jedes Ereignis läuft, mit Flussdiagrammen.
- Fehler: die Fehlermatrix und die Verkettungsregeln.
- Muster und Anti-Muster: wie du Hooks gut strukturierst.
- Beispiele: Rezepte für jeden Anwendungsfall.