Tratamento de erros
Os hooks correm dentro do pedido que observam, por isso a forma como as suas falhas são tratadas importa. Esta página é a política exata.
- As duas políticas
- O que corre quando algo falha
- Encadeamento de exceções
- BaseException
- Erros de configuração
- Avisos
- Escolher uma política
As duas políticas
hooks_raise controla o que acontece quando um hook levanta uma exceção.
hooks_raise |
comportamento |
|---|---|
True (predefinição) |
a exceção do hook propaga-se para fora da chamada. |
False |
o hook é saltado com um RuntimeWarning e a chamada continua. |
Define-se por instância e pode ser substituído por chamada (hooks_raise= em predict_batch,
system_one, Router.route, Router.predict). None por chamada significa «usar o valor da
instância».
# strict: a broken audit hook fails the request
laya.load("convaiinnovations/laya", on_predict_end=audit, hooks_raise=True)
# lenient: telemetry must never take down a served request
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_raise=False)
O dispatch apanha Exception. Tudo o que não seja uma Exception (vê
BaseException) nunca é engolido, mesmo com hooks_raise=False.
O que corre quando algo falha
O ciclo de vida da predição está envolvido em try / except / finally, por isso os hooks de limpeza
correm em caso de falha.
try:
on_predict_start
inference
except BaseException as exc:
ctx.error = exc
on_error (best effort; cannot mask exc)
raise
finally:
elapsed_ms, usage
on_predict_end (best effort; cannot mask exc on the failure path)
Matriz de falhas, por evento e runtime:
| evento | runtime | se levantar exceção |
|---|---|---|
on_predict_start |
Agent / Router | hooks_raise=True: on_error e on_predict_end ainda correm, e depois a exceção propaga-se. False: avisa e continua (as mutações feitas antes da exceção permanecem). |
| inferência | Agent / Router | ctx.error definido, on_error corre, on_predict_end corre, a exceção propaga-se. |
on_error |
Agent / Router | nunca mascara a exceção original; encadeada como __context__. |
on_predict_end (caminho de sucesso) |
Agent / Router | hooks_raise=True: propaga-se (o resultado é calculado mas a chamada falha). False: avisa. |
on_predict_end (caminho de falha) |
Agent / Router | nunca mascara a exceção original; encadeada como __context__. |
on_route |
Router | propaga-se diretamente; ainda não há contexto de predição. |
on_load |
Router | propaga-se diretamente; o checkpoint fica construído e residente. |
on_evict |
Router | propaga-se diretamente; o checkpoint já foi libertado. |
Consequências que vale a pena conhecer:
- Um
on_loadque falha deixa o modelo em cache, por isso oloadseguinte devolve-o sem voltar a disparar oon_load. - Um
on_predict_endque falha no caminho de sucesso significa que o autor da chamada recebe uma exceção em vez de um resultado, ainda que a inferência tenha tido êxito. Usahooks_raise=Falsepara hooks de fim que sejam puros efeitos secundários.
Encadeamento de exceções
Quando um hook falha enquanto outra exceção já se está a propagar, a exceção original é relançada e a
exceção do hook é anexada como __context__. A causa raiz nunca se perde.
class BadTelemetry:
def on_error(self, ctx):
raise RuntimeError("telemetry down")
try:
agent.system_one(state, questions, hooks=[BadTelemetry()])
except RuntimeError as exc:
assert exc.__context__ is not None # the telemetry failure
A mesma regra aplica-se a um on_predict_end que falhe no caminho de falha.
BaseException
O dispatch apanha Exception, não BaseException, por isso KeyboardInterrupt e SystemExit
propagam-se sempre. Ainda assim disparam o ramo except BaseException do ciclo de vida da predição,
o que significa que on_error e on_predict_end correm antes de o processo se desenrolar. Mantém
esses hooks rápidos e não bloqueantes se te importa a latência da interrupção.
Erros de configuração
Uma configuração má falha depressa com TypeError, antes de qualquer inferência:
| caso | levantado em | exemplo |
|---|---|---|
| classe em vez de instância | construção | hooks=[MyHook] |
| nenhum método de ciclo de vida | construção | hooks=[object()] |
| evento não invocável | construção | on_predict_start = 5 |
| hook de conveniência não invocável | construção | on_predict_start=123 |
invocável simples em hooks= |
construção | hooks=[lambda ctx: None] |
Os hooks por chamada são validados quando a chamada é feita, por isso um hook por chamada mau levanta
TypeError a partir de predict/system_one em vez de na construção.
Avisos
Com hooks_raise=False, cada hook que falha emite um RuntimeWarning nomeando o tipo de hook e o
evento:
laya: hook Metrics.on_predict_end failed: connection reset
O aviso é emitido uma vez por falha, não uma vez por definição de hook, por isso um hook instável sob carga pode ser ruidoso. Agrega ou limita a taxa dentro do hook se isso importar.
Tempos limite
hooks_timeout limita cada chamada de hook em segundos. Um hook ainda em execução após o limite é
tratado como uma falha de hook: TimeoutError quando hooks_raise=True, um RuntimeWarning quando
False. None (a predefinição) significa sem limite.
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)
Pode ser definido por instância ou substituído por chamada em predict_batch, system_one,
Router.route, Router.predict e ONNXAgent.system_one. O valor tem de ser positivo; 0 ou um
número negativo levanta ValueError no ponto em que é definido, em vez de competir num join de
comprimento zero.
Um hook com tempo limite corre numa thread worker numa cópia do contexto contextvars do autor da
chamada, por isso um id de pedido ou span de tracing definido pelo autor da chamada é visível para o
hook.
Uma ressalva honesta: o Python não consegue interromper uma thread, por isso um hook que exceda o
tempo continua a correr em segundo plano. O timeout limita quanto tempo o pedido espera, não quanto
tempo o hook vive. Usa-o para manter um pedido servido responsivo, não para recuperar o trabalho.
Para um hook que possa pendurar-se, dá também à chamada subjacente o seu próprio timeout (um timeout
de socket ou HTTP). Como a thread não pode ser recuperada, um hook que se pendura em cada chamada
aumenta as threads uma por chamada; dá a um hook que possa pendurar-se o seu próprio limite em vez de
confiar no hooks_timeout para o parar.
Para um hook assíncrono, a corrotina corre no event loop; um timeout do lado que chama ainda regressa após o limite, e a corrotina continua a correr no ciclo.
O timeout também liberta o lock de hooks_concurrent=False: o dispatch espera pelo hook apenas até
ao limite, e depois avança, enquanto o hook que excedeu o tempo continua a correr fora do lock.
Portanto, o lock serializa os hooks que terminam a tempo, e não todos os hooks que alguma vez foram
iniciados; um hook que se excede já não bloqueia os que estão atrás dele.
Escolher uma política
| tipo de hook | recomendado | porquê |
|---|---|---|
| política / guardrail / censura | hooks_raise=True |
uma política que falha silenciosamente é um buraco de segurança. |
| auditoria / logging | hooks_raise=True em testes, muitas vezes False em produção |
perder um registo de auditoria deve ser alto, mas não necessariamente fatal. |
| métricas / tracing | hooks_raise=False |
a observabilidade não deve falhar o pedido. |
| leitura/escrita de cache | hooks_raise=True |
uma cache avariada deve aparecer, não falhar silenciosamente. |
Podes misturar: instala um guardrail com a predefinição da instância e dá a um hook de telemetria o
seu próprio try/except, ou usa um hooks_raise separado por chamada.
Ver também
- Ciclo de vida: a forma
try/except/finallyem contexto. - Padrões e antipadrões: erros comuns com o tratamento de erros.