Documentación

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

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 uno ValueError en 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_raise y hooks_timeout en un método por llamada tienen None por defecto, lo que significa «usar el valor de la instancia».

  • hooks_concurrent es solo a nivel de instancia.

  • En predict_long los hooks envuelven la inferencia que responde al estado, que para un documento que necesita varias ventanas es el único predict_batch compartido sobre ellas: on_predict_start se dispara una vez, y ctx.states contiene los textos de ventana decodificados en orden de escaneo en lugar del estado del llamador, que se tokenizó para producirlos. states es 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]) 0 ausente – ninguna ventana lo puntuó
    dejó el escaneo tal como se construyó N presente – index, token_start/token_end nombran el tramo decisivo
    reemplazó 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 en route, route_batch, predict y predict_batch se aplica a toda la llamada, incluido on_route. En predict_batch la lista se compone igual que en predict (primero los hooks instalados, luego la lista por llamada; None y [] no añaden nada) y se ejecuta una vez por solicitud.
  • route() es público: llamarlo despacha on_route con los hooks instalados más los hooks por 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_end ve results en la ruta de éxito. En la ruta de fallo, results es None a menos que un hook de inicio los haya definido mediante skip(), y usage por lo tanto también es None (se deriva de results); elapsed_ms siempre se define.
  • on_error se ejecuta antes del bloque finally que calcula elapsed_ms y usage, así que ambos son None ahí. Lee la temporización y el uso desde on_predict_end en su lugar.
  • run_id siempre 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