Referência da API
Tudo nesta página é importável de laya (os nomes comuns) ou de laya.hooks (toda a superfície).
from laya import PredictContext, PredictHook, Hook
from laya.hooks import HOOK_EVENTS, normalise_hooks, dispatch, aggregate_usage
- PredictContext
- Protocolo de Hook
- Tipos de conveniência
- Superfície de configuração
- Payloads de eventos
- Validação
- Ajudantes avançados
PredictContext
Um PredictContext é criado uma vez por chamada pública e passado a cada hook dessa chamada. O
Router.predict_batch é a única exceção: cria um por pedido, como faria uma chamada
Router.predict para esse pedido, por isso os seus hooks ao nível do Router disparam uma vez por
pedido com um estado cada. É mutável: os hooks podem reescrever states, questions e
results, e o on_route pode reescrever decision. Usa igualdade por identidade (eq=False), por
isso um contexto é hasheável e dois contextos nunca são iguais.
@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 | definido quando | mutável | significado |
|---|---|---|---|---|
states |
list |
sempre | sim (início) | os estados para esta chamada. system_one/Router.predict passam um; Agent.predict_batch passa muitos; Router.predict_batch passa um por pedido. Um hook de início pode substituir a lista. |
questions |
dict |
sempre | sim (início) | as perguntas. Um hook de início pode substituir o dict. |
run_id |
str |
sempre | não | um id único partilhado por cada hook desta chamada. Usa-o para correlacionar eventos e spans. |
results |
list | None |
fim (e num skip) | sim (fim) | dicts de resultado por estado, cada um com a forma do retorno de system_one. None até a inferência terminar. |
decision |
dict | None |
apenas Router | sim (route) | o RouteDecision (um dict) que selecionou o checkpoint. |
model |
str | None |
sempre | não | o id do checkpoint: Agent.model_id para um Agent, o alias resolvido (por exemplo "english") para um Router. |
agent |
Agent | ONNXAgent | None |
eventos de predição | não | o runtime que responde à chamada. |
router |
Router | None |
eventos de Router | não | o Router, quando um está em jogo. |
max_len |
int | None |
sempre | sim (início) | orçamento de tokens por chamada para o encoder. None usa a config do agente. |
head_max_len |
int | None |
sempre | sim (início) | orçamento de tokens por chamada para a cabeça de pergunta. None usa a config do agente. |
usage |
dict | None |
fim | sim (fim) | {"input_tokens", "output_tokens"}, somados sobre os estados da chamada. |
started_at |
float |
sempre | não | time.perf_counter() quando a chamada começou. |
elapsed_ms |
float | None |
fim | não | tempo de relógio para toda a chamada, em milissegundos. |
error |
BaseException | None |
caminho de falha | não | a exceção, definida antes de on_error e on_predict_end. |
PredictContext.skip(results)
Faz curto-circuito na inferência. Chamado a partir de on_predict_start, define ctx.results para
que a passagem direta seja saltada; o on_predict_end continua a correr e os resultados fornecidos
são devolvidos.
A chave tem de cobrir tudo de que a resposta depende, e o hook tem de cobrir tudo o que a chamada
transporta: os hooks disparam uma vez por chamada, e o predict_batch chama-os com todos os estados
ao mesmo tempo.
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)
O tests/test_hooks_api.py executa este bloco, o examples/hooks/cache.py e os blocos de cache de
docs/hooks/patterns.md e docs/hooks/examples.md, e afirma as quatro chaves da mesma forma, para
que uma página não possa ensinar uma chave de que o exemplo já se afastou.
No Router, um payload saltado recebe uma chave routing acrescentada (sem sobrepor uma que já
tenha), para que Router.predict mantenha a sua forma de retorno documentada.
Protocolo de Hook
Hook é um typing.Protocol. Implementa qualquer subconjunto dos métodos; o resto é saltado.
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 | onde | corre | pode alterar |
|---|---|---|---|
on_predict_start |
Agent, Router | antes da tokenização/passagem direta | states, questions, ou skip() |
on_predict_end |
Agent, Router | depois de existirem resultados, sucesso ou falha | results |
on_route |
Router | depois da deteção, antes do carregamento | decision |
on_load |
Router | depois de um checkpoint ser construído | nada (observar) |
on_evict |
Router | depois de um checkpoint ser libertado | nada (observar) |
on_error |
Agent, Router | quando uma chamada de predição falha | nada (observar) |
Um hook é livre de definir atributos e métodos extra; só os seis nomes de evento são consultados. Se um hook definir um dos seis como não-invocável, a configuração falha depressa (vê Validação).
BaseHook
BaseHook é a contraparte concreta do protocolo: uma classe com um corpo sem operação para cada
evento. Faz-lhe subclasse e sobrepõe apenas os eventos de que precisas.
from laya import BaseHook
class Audit(BaseHook):
def on_predict_end(self, ctx):
ship(ctx.run_id, ctx.results)
Hook é melhor quando queres tipagem estrutural (qualquer objeto com os métodos certos); BaseHook
é melhor quando queres uma base explícita para fazer subclasse e chamar super().
Tipos de conveniência
PredictHook = Callable[[PredictContext], None]
PredictHook é o tipo de um invocável simples usado com on_predict_start= / on_predict_end=.
Passa um único invocável ou uma sequência deles; cada um é envolvido num hook mínimo.
Registo em runtime
Cada runtime incorpora o HookRegistry, por isso os hooks podem ser acrescentados, removidos ou
limitados após a construção. A mutação é thread-safe; uma chamada lê um snapshot da lista, por isso
acrescentar ou remover um hook nunca perturba uma chamada em voo.
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 aceita os mesmos objetos que hooks= (não invocáveis simples). hooks_installed aceita
qualquer número de objetos hook ou sequências e restaura a lista anterior à saída, incluindo quando
o bloco levanta exceção.
Predefinições para todo o processo
O laya.hooks mantém um pequeno registo para todo o processo, para que um tracer, hook de métricas
ou etiquetador de tenant não tenha de ser passado por todos os Agent e Router. As predefinições
correm primeiro, depois os hooks instalados na instância, depois os hooks por chamada.
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)
As predefinições aplicam-se a cada evento, incluindo os eventos de ciclo de vida do Router on_load
e on_evict. O registo é lido no momento da chamada, por isso os hooks definidos depois de um
Agent ou Router ser construído ainda se aplicam. Não há opt-out por instância; chama
clear_default_hooks() para desligar o conjunto para todo o processo.
Hooks assíncronos
Um evento pode ser uma corrotina. Envolve o hook em AsyncHook e os seus métodos async def correm
até ao fim no core 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())])
Um invocável assíncrono simples passado a on_predict_start= / on_predict_end= também funciona,
porque o dispatch corre qualquer awaitable que um hook devolva.
Onde a corrotina corre:
- Se a thread que chama não tem um ciclo em execução, corre com
asyncio.run. - Se já tem um (um autor de chamada dentro de uma função async), corre num ciclo de fundo dedicado,
para que a thread que chama possa bloquear sem impasse. Passa
AsyncHook(hook, loop=...)para encaminhar para um ciclo específico; tem de estar em execução, e não pode ser o próprio ciclo da thread que chama. Ambos são verificados: um ciclo parado e o próprio ciclo do autor da chamada levantam, cada um,ValueErrorem vez de bloquear para sempre.
Um hook sem métodos async não é afetado.
Superfície de configuração
Cada ponto de entrada aceita os mesmos parâmetros de hook. hooks aceita um objeto ou uma sequência
de objetos; on_predict_start / on_predict_end aceitam um invocável ou uma sequência.
| parâmetro | tipo | predefinição | significado |
|---|---|---|---|
hooks |
Hook | Sequence[Hook] | None |
None |
hooks de ciclo de vida (qualquer dos seis eventos). |
on_predict_start |
PredictHook | Sequence[PredictHook] | None |
None |
invocáveis de conveniência para um evento. |
on_predict_end |
PredictHook | Sequence[PredictHook] | None |
None |
invocáveis de conveniência para um evento. |
hooks_raise |
bool |
True |
True: uma exceção de um hook propaga-se. False: avisa e continua. |
hooks_concurrent |
bool |
True |
False: despacha os hooks sob um lock, um de cada vez. |
hooks_timeout |
float | None |
None |
limite de tempo por hook em segundos; None significa sem 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_raiseehooks_timeoutnum método por chamada assumemNonepor predefinição, o que significa «usar o valor da instância». -
hooks_concurrenté apenas ao nível da instância. -
No
predict_longos hooks envolvem a inferência que responde ao estado, que para um documento que precisa de várias janelas é o únicopredict_batchpartilhado sobre elas: oon_predict_startdispara uma vez, e octx.statescontém os textos das janelas descodificados pela ordem de varrimento, e não o estado do autor da chamada, que foi tokenizado para os produzir. Ostatesé mutável a partir de um hook de início, por isso o varrimento que chega à inferência não tem de ser o split que opredict_longcalculou. O que muda é aquilo que a resposta pode alegar:o que o hook de início fez usage["windows"]answer["window"]respondeu com ctx.skip([result])0ausente – nenhuma janela o pontuou deixou o varrimento tal como foi construído Npresente – index,token_start/token_endnomeiam o span decisivosubstituiu o varrimento, de qualquer forma os estados que foram pontuados ausente – os offsets descrevem as janelas do predict_long, não o texto que foi pontuadousage["windows"]é o total em todos os caminhos, incluindo aquele em que um estado já cabia numa única janela, por isso uma resposta em cache nunca se lê como uma janela que o modelo leu.
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=por chamada emroute,route_batch,predictepredict_batchaplicam-se a toda a chamada, incluindo oon_route. Empredict_batcha lista é composta da mesma forma que empredict(primeiro os hooks instalados, depois a lista por chamada;Nonee[]não acrescentam nada) e corre uma vez por pedido.route()é público: chamá-lo despacha oon_routecom os hooks instalados mais quaisquerhookspor chamada.
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
O ONNXAgent não tem Router, por isso expõe apenas os eventos ao nível da predição.
Payloads de eventos
Que campos são preenchidos, por evento e 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 | ✓ | ✓ | – | ✓ | ✓ | – | ✓ | em sucesso | ✓ | em falha |
on_predict_end |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | em sucesso | ✓ | em falha |
on_error |
ambos | ✓ | ✓ | ✓ (Router) | ✓ | ✓ | ✓ (Router) | – | – | – | ✓ |
on_route |
Router | ✓ | ✓ | ✓ | – | – | ✓ | – | – | – | – |
on_load |
Router | [] |
{} |
– | ✓ | ✓ | ✓ | – | – | – | – |
on_evict |
Router | [] |
{} |
– | ✓ | – | ✓ | – | – | – | – |
Detalhes de temporização:
on_predict_endvêresultsno caminho de sucesso. No caminho de falha,resultséNonea menos que um hook de início os tenha definido viaskip(), eusageé, portanto, tambémNone(é derivado deresults);elapsed_msé sempre definido.on_errorcorre antes do blocofinallyque calculaelapsed_mseusage, por isso ambos sãoNoneaí. Lê antes a temporização e o usage a partir deon_predict_end.run_idé sempre preenchido.
Validação
A configuração é validada quando os hooks são normalizados, o que acontece na construção para os
hooks instalados e no momento da chamada para os hooks por chamada. Estes levantam TypeError:
| caso | mensagem |
|---|---|
| é passada uma classe em vez de uma instância | hooks entries must be instances, not classes; ... |
| um objeto não implementa nenhum dos seis eventos | hooks entries must implement at least one of ... |
| um atributo de evento não é invocável | hooks entry X.on_predict_start must be callable, got int |
on_predict_start= / on_predict_end= não é invocável |
on_predict_start must be callable, got int |
hooks= não aceita invocáveis simples, porque um invocável nu não diz para que evento é. Usa
on_predict_start= / on_predict_end= para esses.
Ajudantes avançados
Estes são usados internamente e são estáveis, mas a maioria dos utilizadores não precisa deles.
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 achata um objeto/sequência hooks e os dois invocáveis numa única lista ordenada.
dispatch chama event em cada hook que o implementa, aplicando a política de exceções, o lock e o
timeout, e corre o resultado de um hook se for awaitable. run_coroutine_sync corre um awaitable até
ao fim a partir de código síncrono, no ciclo do autor da chamada se estiver livre, ou num ciclo de
fundo se o autor da chamada já tiver um. aggregate_usage soma blocos de usage 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)
Ver também
- Ciclo de vida: quando corre cada evento, com fluxogramas.
- Erros: a matriz de falhas e as regras de encadeamento.
- Padrões e antipadrões: como estruturar bem os hooks.
- Exemplos: receitas para cada caso de uso.