Dokumentation

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

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.run ausgefü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 jeweils ValueError aus, 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_raise und hooks_timeout auf einer Methode pro Aufruf haben standardmäßig None, was „den Wert der Instanz verwenden“ bedeutet.

  • hooks_concurrent gibt es nur auf Instanzebene.

  • Bei predict_long umschließen die Hooks die Inferenz, die den Zustand beantwortet; für ein Dokument, das mehrere Fenster braucht, ist das das eine gemeinsame predict_batch über sie: on_predict_start wird einmal ausgelöst, und ctx.states enthält die dekodierten Fenstertexte in Scan-Reihenfolge statt des Zustands des Aufrufers, der tokenisiert wurde, um sie zu erzeugen. states ist von einem Start-Hook aus veränderbar, sodass der Scan, der die Inferenz erreicht, nicht die von predict_long berechnete 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]) 0 fehlt – kein Fenster hat es bewertet
    ließ den Scan so, wie er gebaut wurde N vorhanden – index, token_start/token_end benennen den entscheidenden Span
    ersetzte 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 wurde

    usage["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 bei route, route_batch, predict und predict_batch gilt für den gesamten Aufruf, einschließlich on_route. Bei predict_batch wird die Liste genauso zusammengesetzt wie bei predict (zuerst die installierten Hooks, dann die Liste pro Aufruf; None und [] fügen nichts hinzu) und läuft einmal pro Anfrage.
  • route() ist öffentlich: ein Aufruf löst on_route mit den installierten Hooks plus allen hooks pro 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_end sieht results auf dem Erfolgspfad. Auf dem Fehlerpfad ist results None, es sei denn, ein Start-Hook hat sie über skip() gesetzt, und usage ist daher ebenfalls None (es wird von results abgeleitet); elapsed_ms ist immer gesetzt.
  • on_error läuft vor dem finally-Block, der elapsed_ms und usage berechnet, sodass beide dort None sind. Lies Timing und Nutzung stattdessen aus on_predict_end.
  • run_id ist 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