Referencia de la API
Todo lo que hay en esta página se puede importar desde laya (los nombres comunes) o desde laya.hooks (toda la superficie).
from laya import PredictContext, PredictHook, Hook
from laya.hooks import HOOK_EVENTS, normalise_hooks, dispatch, aggregate_usage
- PredictContext
- Protocolo de Hook
- Tipos de conveniencia
- Superficie de configuración
- Payloads de eventos
- Validación
- Ayudantes avanzados
PredictContext
Un PredictContext se crea una vez por llamada pública y se pasa a cada hook de esa llamada.
Router.predict_batch es la única excepción: crea uno por solicitud, como lo haría una llamada a
Router.predict para esa solicitud, así que sus hooks de nivel de Router se disparan una vez por
solicitud con un estado cada uno. Es mutable: los hooks pueden reescribir states, questions y results, y on_route puede
reescribir decision. Usa igualdad por identidad (eq=False), así que un contexto es hashable y dos
contextos nunca son iguales.
@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
| campo | tipo | se define cuando | mutable | significado |
|---|---|---|---|---|
states |
list |
siempre | sí (inicio) | los estados de esta llamada. system_one/Router.predict pasan uno; Agent.predict_batch pasa muchos; Router.predict_batch pasa uno por solicitud. Un hook de inicio puede reemplazar la lista. |
questions |
dict |
siempre | sí (inicio) | las preguntas. Un hook de inicio puede reemplazar el dict. |
run_id |
str |
siempre | no | un id único compartido por cada hook de esta llamada. Úsalo para correlacionar eventos y spans. |
results |
list | None |
fin (y en un skip) | sí (fin) | dicts de resultado por estado, cada uno con la forma del valor devuelto por system_one. None hasta que termina la inferencia. |
decision |
dict | None |
solo Router | sí (route) | el RouteDecision (un dict) que seleccionó el checkpoint. |
model |
str | None |
siempre | no | el id del checkpoint: Agent.model_id para un Agent, el alias resuelto (por ejemplo "english") para un Router. |
agent |
Agent | ONNXAgent | None |
eventos de predict | no | el runtime que responde a la llamada. |
router |
Router | None |
eventos de Router | no | el Router, cuando hay uno en juego. |
max_len |
int | None |
siempre | sí (inicio) | presupuesto de tokens por llamada para el encoder. None usa la config del agent. |
head_max_len |
int | None |
siempre | sí (inicio) | presupuesto de tokens por llamada para la cabeza de pregunta. None usa la config del agent. |
usage |
dict | None |
fin | sí (fin) | {"input_tokens", "output_tokens"}, sumados sobre los estados de la llamada. |
started_at |
float |
siempre | no | time.perf_counter() cuando comenzó la llamada. |
elapsed_ms |
float | None |
fin | no | tiempo de reloj de toda la llamada, en milisegundos. |
error |
BaseException | None |
ruta de fallo | no | la excepción, definida antes de on_error y on_predict_end. |
PredictContext.skip(results)
Corta la inferencia en seco. Llamado desde on_predict_start, define ctx.results para que se omita la
pasada hacia adelante; on_predict_end se ejecuta igualmente y se devuelven los resultados
proporcionados.
La clave tiene que cubrir todo aquello de lo que depende la respuesta, y el hook tiene que cubrir todo lo
que lleva la llamada: los hooks se disparan una vez por llamada, y predict_batch los llama con todos los
estados a la vez.
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 ejecuta este bloque, examples/hooks/cache.py, y los bloques de caché de
docs/hooks/patterns.md y docs/hooks/examples.md, y comprueba las cuatro claves de la misma forma, así
que una página no puede enseñar una clave de la que el ejemplo ya se ha alejado.
En el Router, a un payload omitido se le añade una clave routing (sin sobrescribir una que ya
tenga), así que Router.predict conserva la forma de retorno documentada.
Protocolo de Hook
Hook es un typing.Protocol. Implementa cualquier subconjunto de los métodos; el resto se omite.
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: ...
| evento | dónde | se ejecuta | puede cambiar |
|---|---|---|---|
on_predict_start |
Agent, Router | antes de la tokenización y la pasada hacia adelante | states, questions, o skip() |
on_predict_end |
Agent, Router | después de que existan los resultados, con éxito o fallo | results |
on_route |
Router | después de la detección, antes de cargar | decision |
on_load |
Router | después de construir un checkpoint | nada (observar) |
on_evict |
Router | después de liberar un checkpoint | nada (observar) |
on_error |
Agent, Router | cuando falla una llamada a predict | nada (observar) |
Un hook es libre de definir atributos y métodos extra; solo se consultan los seis nombres de evento. Si un hook define uno de los seis como algo no invocable, la configuración falla rápido (consulta Validación).
BaseHook
BaseHook es la contraparte concreta del protocolo: una clase con un cuerpo no-op para cada evento.
Crea una subclase y sobrescribe solo los eventos que necesites.
from laya import BaseHook
class Audit(BaseHook):
def on_predict_end(self, ctx):
ship(ctx.run_id, ctx.results)
Hook es mejor cuando quieres tipado estructural (cualquier objeto con los métodos correctos);
BaseHook es mejor cuando quieres una base explícita de la que heredar y sobre la que llamar a
super().
Tipos de conveniencia
PredictHook = Callable[[PredictContext], None]
PredictHook es el tipo de un invocable simple que se usa con on_predict_start= / on_predict_end=.
Pasa un único invocable o una secuencia de ellos; cada uno se envuelve en un hook mínimo.
Registro en runtime
Cada runtime mezcla HookRegistry, así que los hooks se pueden añadir, eliminar o acotar después de la
construcción. La mutación es thread-safe; una llamada lee una instantánea de la lista, así que añadir o
eliminar un hook nunca perturba una llamada en curso.
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 acepta los mismos objetos que hooks= (no invocables simples). hooks_installed toma
cualquier número de objetos hook o secuencias y restaura la lista anterior al salir, incluso cuando el
bloque lanza una excepción.
Predeterminados para todo el proceso
laya.hooks mantiene un pequeño registro para todo el proceso, así que un tracer, un hook de métricas o
un etiquetador de tenant no tienen que pasarse a través de cada Agent y Router. Los predeterminados
se ejecutan primero, luego los hooks instalados en la instancia, y luego los hooks por llamada.
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)
Los predeterminados se aplican a cada evento, incluidos los eventos del ciclo de vida del Router on_load
y on_evict. El registro se lee en el momento de la llamada, así que los hooks definidos después de
construir un Agent o un Router siguen aplicándose. No hay forma de excluirse por instancia; llama a
clear_default_hooks() para desactivar el conjunto de todo el proceso.
Hooks asíncronos
Un evento puede ser una corrutina. Envuelve el hook en AsyncHook y sus métodos async def se ejecutan
hasta completarse en el núcleo síncrono:
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 invocable async simple pasado a on_predict_start= / on_predict_end= también funciona, porque
dispatch ejecuta cualquier awaitable que devuelva un hook.
Dónde se ejecuta la corrutina:
- Si el hilo que llama no tiene un bucle en ejecución, se ejecuta con
asyncio.run. - Si ya tiene uno (un llamador dentro de una función async), se ejecuta en un bucle de fondo dedicado,
así que el hilo que llama puede bloquearse sin interbloquearse. Pasa
AsyncHook(hook, loop=...)para canalizarlo a un bucle concreto; debe estar en ejecución, y no debe ser el propio bucle del hilo que llama. Ambos se comprueban: un bucle detenido y el propio bucle del llamador lanzan cada unoValueErroren lugar de bloquearse para siempre.
Un hook sin métodos async no se ve afectado.
Superficie de configuración
Cada punto de entrada acepta los mismos parámetros de hook. hooks toma un objeto o una secuencia de
objetos; on_predict_start / on_predict_end toman un invocable o una secuencia.
| parámetro | tipo | predeterminado | significado |
|---|---|---|---|
hooks |
Hook | Sequence[Hook] | None |
None |
hooks del ciclo de vida (cualquiera de los seis eventos). |
on_predict_start |
PredictHook | Sequence[PredictHook] | None |
None |
invocables de conveniencia para un evento. |
on_predict_end |
PredictHook | Sequence[PredictHook] | None |
None |
invocables de conveniencia para un evento. |
hooks_raise |
bool |
True |
True: una excepción de un hook se propaga. False: avisa y continúa. |
hooks_concurrent |
bool |
True |
False: despacha los hooks bajo un lock, de uno en uno. |
hooks_timeout |
float | None |
None |
límite de tiempo por hook en segundos; None significa sin límite. |
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_raiseyhooks_timeouten un método por llamada tienenNonepor defecto, lo que significa «usar el valor de la instancia». -
hooks_concurrentes solo a nivel de instancia. -
En
predict_longlos hooks envuelven la inferencia que responde al estado, que para un documento que necesita varias ventanas es el únicopredict_batchcompartido sobre ellas:on_predict_startse dispara una vez, yctx.statescontiene los textos de ventana decodificados en orden de escaneo en lugar del estado del llamador, que se tokenizó para producirlos.stateses mutable desde un hook de inicio, así que el escaneo que llega a la inferencia no tiene por qué ser el fragmentado que calculópredict_long. Lo que cambia es lo que puede afirmar la respuesta:qué hizo el hook de inicio usage["windows"]answer["window"]respondió con ctx.skip([result])0ausente – ninguna ventana lo puntuó dejó el escaneo tal como se construyó Npresente – index,token_start/token_endnombran el tramo decisivoreemplazó el escaneo, de cualquier forma los estados que se puntuaron ausente – los offsets describen las ventanas de predict_long, no el texto que se puntuóusage["windows"]es total sobre cada ruta, incluida aquella en la que un estado ya cabía en una sola ventana, así que una respuesta cacheada nunca se lee como una ventana que el modelo leyó.
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
- El
hooks=por llamada enroute,route_batch,predictypredict_batchse aplica a toda la llamada, incluidoon_route. Enpredict_batchla lista se compone igual que enpredict(primero los hooks instalados, luego la lista por llamada;Noney[]no añaden nada) y se ejecuta una vez por solicitud. route()es público: llamarlo despachaon_routecon los hooks instalados más loshookspor llamada que haya.
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 no tiene Router, así que solo expone los eventos de nivel de predict.
Payloads de eventos
Qué campos se rellenan, por evento y por runtime:
| evento | runtime | states |
questions |
decision |
model |
agent |
router |
results |
usage |
elapsed_ms |
error |
|---|---|---|---|---|---|---|---|---|---|---|---|
on_predict_start |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | – | – | – | – |
on_predict_start |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – | – | – |
on_predict_end |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | ✓ | si hay éxito | ✓ | si hay fallo |
on_predict_end |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | si hay éxito | ✓ | si hay fallo |
on_error |
ambos | ✓ | ✓ | ✓ (Router) | ✓ | ✓ | ✓ (Router) | – | – | – | ✓ |
on_route |
Router | ✓ | ✓ | ✓ | – | – | ✓ | – | – | – | – |
on_load |
Router | [] |
{} |
– | ✓ | ✓ | ✓ | – | – | – | – |
on_evict |
Router | [] |
{} |
– | ✓ | – | ✓ | – | – | – | – |
Detalles de temporización:
on_predict_endveresultsen la ruta de éxito. En la ruta de fallo,resultsesNonea menos que un hook de inicio los haya definido medianteskip(), yusagepor lo tanto también esNone(se deriva deresults);elapsed_mssiempre se define.on_errorse ejecuta antes del bloquefinallyque calculaelapsed_msyusage, así que ambos sonNoneahí. Lee la temporización y el uso desdeon_predict_enden su lugar.run_idsiempre está relleno.
Validación
La configuración se valida cuando se normalizan los hooks, lo que ocurre en la construcción para los
hooks instalados y en el momento de la llamada para los hooks por llamada. Esto lanza TypeError:
| caso | mensaje |
|---|---|
| se pasa una clase en lugar de una instancia | hooks entries must be instances, not classes; ... |
| un objeto no implementa ninguno de los seis eventos | hooks entries must implement at least one of ... |
| un atributo de evento no es invocable | hooks entry X.on_predict_start must be callable, got int |
on_predict_start= / on_predict_end= no es invocable |
on_predict_start must be callable, got int |
hooks= no acepta invocables simples, porque un invocable desnudo no dice para qué evento es. Usa
on_predict_start= / on_predict_end= para esos.
Ayudantes avanzados
Se usan internamente y son estables, pero la mayoría de los usuarios no los necesita.
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 aplana un objeto/secuencia hooks y los dos invocables en una lista ordenada.
dispatch llama a event en cada hook que lo implementa, aplicando la política de raise, el lock y el
timeout, y ejecuta el resultado de un hook si es awaitable. run_coroutine_sync ejecuta un awaitable
hasta completarse desde código síncrono, en el bucle del llamador si está libre, o en un bucle de fondo
si el llamador ya tiene uno. aggregate_usage suma los bloques de uso por estado.
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)
Véase también
- Ciclo de vida: cuándo se ejecuta cada evento, con diagramas de flujo.
- Errores: la matriz de fallos y las reglas de encadenamiento.
- Patrones y antipatrones: cómo estructurar bien los hooks.
- Ejemplos: recetas para cada caso de uso.