Documentação

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

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_load que falha deixa o modelo em cache, por isso o load seguinte devolve-o sem voltar a disparar o on_load.
  • Um on_predict_end que 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. Usa hooks_raise=False para 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