Référence de l’API
Tout sur cette page s’importe depuis laya (les noms courants) ou laya.hooks (toute la
surface).
from laya import PredictContext, PredictHook, Hook
from laya.hooks import HOOK_EVENTS, normalise_hooks, dispatch, aggregate_usage
- PredictContext
- Protocole de hook
- Types pratiques
- Surface de configuration
- Charges utiles des événements
- Validation
- Assistants avancés
PredictContext
Un PredictContext est créé une fois par appel public et passé à chaque hook de cet appel.
Router.predict_batch est la seule exception : il en crée un par requête, comme le ferait un appel
Router.predict pour cette requête, donc ses hooks au niveau Router se déclenchent une fois par
requête avec un état chacun. Il est mutable : les hooks peuvent réécrire states, questions et
results, et on_route peut réécrire decision. Il utilise l’égalité par identité (eq=False),
donc un contexte est hachable et deux contextes ne sont jamais égaux.
@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
| champ | type | défini quand | mutable | signification |
|---|---|---|---|---|
states |
list |
toujours | oui (start) | les états de cet appel. system_one/Router.predict en passent un ; Agent.predict_batch en passe plusieurs ; Router.predict_batch en passe un par requête. Un hook de start peut remplacer la liste. |
questions |
dict |
toujours | oui (start) | les questions. Un hook de start peut remplacer le dict. |
run_id |
str |
toujours | non | un id unique partagé par chaque hook de cet appel. Utilise-le pour corréler les événements et les spans. |
results |
list | None |
end (et lors d’un skip) | oui (end) | les dicts de résultat par état, chacun de la forme du retour de system_one. None jusqu’à la fin de l’inférence. |
decision |
dict | None |
Router uniquement | oui (route) | le RouteDecision (un dict) qui a sélectionné le checkpoint. |
model |
str | None |
toujours | non | l’id du checkpoint : Agent.model_id pour un Agent, l’alias résolu (par exemple "english") pour un Router. |
agent |
Agent | ONNXAgent | None |
événements predict | non | le runtime qui répond à l’appel. |
router |
Router | None |
événements Router | non | le Router, quand il y en a un en jeu. |
max_len |
int | None |
toujours | oui (start) | budget de jetons par appel pour l’encodeur. None utilise la config de l’agent. |
head_max_len |
int | None |
toujours | oui (start) | budget de jetons par appel pour la tête de question. None utilise la config de l’agent. |
usage |
dict | None |
end | oui (end) | {"input_tokens", "output_tokens"}, additionnés sur les états de l’appel. |
started_at |
float |
toujours | non | time.perf_counter() quand l’appel a commencé. |
elapsed_ms |
float | None |
end | non | temps mural de tout l’appel, en millisecondes. |
error |
BaseException | None |
chemin d’échec | non | l’exception, définie avant on_error et on_predict_end. |
PredictContext.skip(results)
Court-circuite l’inférence. Appelé depuis on_predict_start, il définit ctx.results pour que la passe
avant soit ignorée ; on_predict_end tourne quand même et les résultats fournis sont renvoyés.
La clé doit couvrir tout ce dont la réponse dépend, et le hook doit couvrir tout ce que l’appel
transporte : les hooks se déclenchent une fois par appel, et predict_batch les appelle avec tous les
états à la fois.
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 exécute ce bloc, examples/hooks/cache.py, et les blocs de mise en cache de
docs/hooks/patterns.md et docs/hooks/examples.md, et asserte les quatre clés de la même façon,
donc une page ne peut pas enseigner une clé dont l’exemple a évolué.
Sur le Router, une charge utile ignorée se voit ajouter une clé routing (sans écraser une clé déjà
présente), pour que Router.predict garde sa forme de retour documentée.
Protocole de hook
Hook est un typing.Protocol. Implémente n’importe quel sous-ensemble des méthodes ; les autres sont
ignorées.
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: ...
| événement | où | s’exécute | peut changer |
|---|---|---|---|
on_predict_start |
Agent, Router | avant la tokenisation/la passe avant | states, questions, ou skip() |
on_predict_end |
Agent, Router | une fois les résultats là, succès ou échec | results |
on_route |
Router | après la détection, avant le chargement | decision |
on_load |
Router | après la construction d’un checkpoint | rien (observer) |
on_evict |
Router | après la libération d’un checkpoint | rien (observer) |
on_error |
Agent, Router | quand un appel predict échoue | rien (observer) |
Un hook est libre de définir des attributs et méthodes supplémentaires ; seuls les six noms d’événement sont consultés. Si un hook définit l’un des six comme non appelable, la configuration échoue immédiatement (voir Validation).
BaseHook
BaseHook est la contrepartie concrète du protocole : une classe avec un corps sans effet pour chaque
événement. Hérite-en et ne redéfinis que les événements dont tu as besoin.
from laya import BaseHook
class Audit(BaseHook):
def on_predict_end(self, ctx):
ship(ctx.run_id, ctx.results)
Hook est préférable quand tu veux du typage structurel (n’importe quel objet avec les bonnes
méthodes) ; BaseHook est préférable quand tu veux une base explicite à hériter et sur laquelle
appeler super().
Types pratiques
PredictHook = Callable[[PredictContext], None]
PredictHook est le type d’un appelable simple utilisé avec on_predict_start= / on_predict_end=.
Passe un seul appelable ou une séquence ; chacun est enveloppé dans un hook minimal.
Enregistrement à l’exécution
Chaque runtime hérite de HookRegistry, donc des hooks peuvent être ajoutés, retirés ou limités à un
bloc après la construction. La mutation est sûre entre threads ; un appel lit un instantané de la
liste, donc ajouter ou retirer un hook ne perturbe jamais un appel en cours.
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 accepte les mêmes objets que hooks= (pas les appelables simples). hooks_installed prend
un nombre quelconque d’objets hook ou de séquences et restaure la liste précédente à la sortie, y
compris quand le bloc lève une erreur.
Valeurs par défaut pour tout le processus
laya.hooks tient un petit registre à l’échelle du processus, pour qu’un traceur, un hook de métriques
ou un tagueur de locataire n’ait pas à être passé à chaque Agent et Router. Les valeurs par défaut
tournent en premier, puis les hooks installés sur l’instance, puis les hooks par appel.
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)
Les valeurs par défaut s’appliquent à chaque événement, y compris les événements de cycle de vie du
Router on_load et on_evict. Le registre est lu au moment de l’appel, donc les hooks définis après
la construction d’un Agent ou d’un Router s’appliquent quand même. Il n’y a pas d’opt-out par
instance ; appelle clear_default_hooks() pour désactiver l’ensemble à l’échelle du processus.
Hooks asynchrones
Un événement peut être une coroutine. Enveloppe le hook dans AsyncHook et ses méthodes async def
s’exécutent jusqu’au bout dans le cœur synchrone :
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())])
Un appelable asynchrone simple passé à on_predict_start= / on_predict_end= fonctionne aussi, parce
que dispatch exécute tout awaitable que renvoie un hook.
Où tourne la coroutine :
- Si le thread appelant n’a pas de boucle en cours, elle tourne avec
asyncio.run. - S’il en a déjà une (un appelant dans une fonction async), elle tourne sur une boucle d’arrière-plan
dédiée, pour que le thread appelant puisse bloquer sans interbloquer. Passe
AsyncHook(hook, loop=...)pour la canaliser vers une boucle précise ; elle doit tourner, et ne pas être la propre boucle du thread appelant. Les deux sont vérifiés : une boucle arrêtée et la propre boucle de l’appelant lèvent chacuneValueErrorau lieu de bloquer pour toujours.
Un hook sans méthode async n’est pas affecté.
Surface de configuration
Chaque point d’entrée accepte les mêmes paramètres de hook. hooks prend un objet ou une séquence
d’objets ; on_predict_start / on_predict_end prennent un appelable ou une séquence.
| paramètre | type | défaut | signification |
|---|---|---|---|
hooks |
Hook | Sequence[Hook] | None |
None |
hooks de cycle de vie (n’importe lequel des six événements). |
on_predict_start |
PredictHook | Sequence[PredictHook] | None |
None |
appelables de commodité pour un événement. |
on_predict_end |
PredictHook | Sequence[PredictHook] | None |
None |
appelables de commodité pour un événement. |
hooks_raise |
bool |
True |
True : une exception de hook se propage. False : avertir et continuer. |
hooks_concurrent |
bool |
True |
False : dispatcher les hooks sous un verrou, un à la fois. |
hooks_timeout |
float | None |
None |
limite de temps par hook en secondes ; None signifie aucune limite. |
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_raiseethooks_timeoutsur une méthode par appel valentNonepar défaut, ce qui signifie « utilise la valeur de l’instance ». -
hooks_concurrentest au niveau de l’instance uniquement. -
Sur
predict_long, les hooks enveloppent l’inférence qui répond à l’état, laquelle, pour un document ayant besoin de plusieurs fenêtres, est lepredict_batchunique et partagé sur ces fenêtres :on_predict_startse déclenche une fois, etctx.statescontient les textes de fenêtre décodés dans l’ordre du balayage plutôt que l’état de l’appelant, qui a été tokenisé pour les produire.statesest mutable depuis un hook de start, donc le balayage qui atteint l’inférence n’est pas forcément le découpage calculé parpredict_long. Ce qui change, c’est ce que la réponse peut affirmer :ce qu’a fait le hook de start usage["windows"]answer["window"]a répondu avec ctx.skip([result])0absent – aucune fenêtre ne l’a évalué a laissé le balayage tel que construit Nprésent – index,token_start/token_endnomment le span décisifa remplacé le balayage, de n’importe quelle façon les états qui ont été évalués absent – les offsets décrivent les fenêtres de predict_long, pas le texte qui a été évaluéusage["windows"]est un total sur tous les chemins, y compris celui où un état tenait déjà dans une seule fenêtre, donc une réponse en cache ne se lit jamais comme une fenêtre que le modèle a lue.
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
hooks=par appel surroute,route_batch,predictetpredict_batchs’applique à tout l’appel, y comprison_route. Surpredict_batch, la liste est composée comme surpredict(d’abord les hooks installés, puis la liste par appel.Noneet[]n’ajoutent rien) et s’exécute une fois par requête.route()est public : l’appeler dispatcheon_routeavec les hooks installés plus touthookspar appel.
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 n’a pas de Router, donc il n’expose que les événements au niveau predict.
Charges utiles des événements
Quels champs sont remplis, par événement et par runtime :
| événement | runtime | states |
questions |
decision |
model |
agent |
router |
results |
usage |
elapsed_ms |
error |
|---|---|---|---|---|---|---|---|---|---|---|---|
on_predict_start |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | – | – | – | – |
on_predict_start |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – | – | – |
on_predict_end |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | ✓ | au succès | ✓ | à l’échec |
on_predict_end |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | au succès | ✓ | à l’échec |
on_error |
les deux | ✓ | ✓ | ✓ (Router) | ✓ | ✓ | ✓ (Router) | – | – | – | ✓ |
on_route |
Router | ✓ | ✓ | ✓ | – | – | ✓ | – | – | – | – |
on_load |
Router | [] |
{} |
– | ✓ | ✓ | ✓ | – | – | – | – |
on_evict |
Router | [] |
{} |
– | ✓ | – | ✓ | – | – | – | – |
Détails de timing :
on_predict_endvoitresultssur le chemin du succès. Sur le chemin de l’échec,resultsestNonesauf si un hook de start les a définis viaskip(), etusageest doncNoneaussi (il est dérivé deresults) ;elapsed_msest toujours défini.on_errortourne avant le blocfinallyqui calculeelapsed_msetusage, donc les deux sontNoneà ce moment-là. Lis le timing et l’usage depuison_predict_endà la place.run_idest toujours rempli.
Validation
La configuration est validée quand les hooks sont normalisés, ce qui a lieu à la construction pour les
hooks installés et au moment de l’appel pour les hooks par appel. Ces cas lèvent TypeError :
| cas | message |
|---|---|
| une classe est passée au lieu d’une instance | hooks entries must be instances, not classes; ... |
| un objet n’implémente aucun des six événements | hooks entries must implement at least one of ... |
| un attribut d’événement n’est pas appelable | hooks entry X.on_predict_start must be callable, got int |
on_predict_start= / on_predict_end= n’est pas appelable |
on_predict_start must be callable, got int |
hooks= n’accepte pas les appelables simples, parce qu’un appelable nu ne dit pas pour quel
événement il est. Utilise on_predict_start= / on_predict_end= pour ceux-là.
Assistants avancés
Ceux-ci sont utilisés en interne et sont stables, mais la plupart des utilisateurs n’en ont pas besoin.
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 aplatit un objet/séquence hooks et les deux appelables en une seule liste ordonnée.
dispatch appelle event sur chaque hook qui l’implémente, en appliquant la politique de levée, le
verrou et le timeout, et exécute le résultat d’un hook s’il est awaitable. run_coroutine_sync exécute
un awaitable jusqu’au bout depuis du code synchrone, sur la boucle de l’appelant si elle est libre, ou
sur une boucle d’arrière-plan si l’appelant en a déjà une. aggregate_usage additionne les blocs
d’usage par état.
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)
Voir aussi
- Cycle de vie : quand chaque événement tourne, avec des organigrammes.
- Erreurs : la matrice de défaillance et les règles d’enchaînement.
- Motifs et anti-motifs : comment bien structurer les hooks.
- Exemples : des recettes pour chaque cas d’usage.