Manejo de errores
Los hooks se ejecutan dentro de la solicitud que observan, así que importa cómo se manejan sus fallos. Esta página describe la política exacta.
- Las dos políticas
- Qué se ejecuta cuando algo falla
- Encadenamiento de excepciones
- BaseException
- Errores de configuración
- Advertencias
- Elección de una política
Las dos políticas
hooks_raise controla qué ocurre cuando un hook lanza una excepción.
hooks_raise |
comportamiento |
|---|---|
True (predeterminado) |
la excepción del hook se propaga fuera de la llamada. |
False |
el hook se omite con un RuntimeWarning y la llamada continúa. |
Se define por instancia y se puede sobrescribir por llamada (hooks_raise= en predict_batch,
system_one, Router.route, Router.predict). Un None por llamada significa “usar el valor de la instancia”.
# 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)
dispatch captura Exception. Todo lo que no sea una Exception (consulta
BaseException) nunca se silencia, ni siquiera con hooks_raise=False.
Qué se ejecuta cuando algo falla
El ciclo de vida de predict está envuelto en try / except / finally, así que los hooks de limpieza
se ejecutan cuando hay un fallo.
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 fallos, por evento y runtime:
| evento | runtime | si lanza una excepción |
|---|---|---|
on_predict_start |
Agent / Router | hooks_raise=True: on_error y on_predict_end todavía se ejecutan, y luego la excepción se propaga. False: avisa y continúa (las mutaciones hechas antes del lanzamiento se mantienen). |
| inference | Agent / Router | ctx.error se establece, on_error se ejecuta, on_predict_end se ejecuta, la excepción se propaga. |
on_error |
Agent / Router | nunca enmascara la excepción original; se encadena como __context__. |
on_predict_end (ruta de éxito) |
Agent / Router | hooks_raise=True: se propaga (el resultado se calcula, pero la llamada falla). False: avisa. |
on_predict_end (ruta de fallo) |
Agent / Router | nunca enmascara la excepción original; se encadena como __context__. |
on_route |
Router | se propaga directamente; todavía no hay un contexto de predict. |
on_load |
Router | se propaga directamente; el checkpoint permanece construido y residente. |
on_evict |
Router | se propaga directamente; el checkpoint ya se ha liberado. |
Consecuencias que conviene conocer:
- Un
on_loadque falla deja el modelo en caché, así que el siguienteloadlo devuelve sin volver a dispararon_load. - Un
on_predict_endque falla en la ruta de éxito significa que quien llama recibe una excepción en lugar de un resultado, aunque la inferencia haya tenido éxito. Usahooks_raise=Falsepara los hooks finales que son efectos secundarios puros.
Encadenamiento de excepciones
Cuando un hook falla mientras ya se está propagando otra excepción, la excepción original se
vuelve a lanzar y la del hook se adjunta como __context__. La causa raíz nunca se pierde.
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
La misma regla se aplica a un on_predict_end que falla en la ruta de fallo.
BaseException
dispatch captura Exception, no BaseException, así que KeyboardInterrupt y SystemExit
siempre se propagan. Aun así, activan la rama except BaseException del ciclo de vida de predict,
lo que significa que on_error y on_predict_end se ejecutan antes de que el proceso se desmonte.
Mantén esos hooks rápidos y sin bloqueos si te importa la latencia de interrupción.
Errores de configuración
Una configuración incorrecta falla rápido con TypeError, antes de cualquier inferencia:
| caso | se lanza en | ejemplo |
|---|---|---|
| clase en lugar de instancia | construcción | hooks=[MyHook] |
| sin método de ciclo de vida | construcción | hooks=[object()] |
| evento no invocable | construcción | on_predict_start = 5 |
| hook de conveniencia no invocable | construcción | on_predict_start=123 |
invocable simple en hooks= |
construcción | hooks=[lambda ctx: None] |
Los hooks por llamada se validan cuando se hace la llamada, así que un hook por llamada incorrecto
lanza TypeError desde predict/system_one en lugar de en la construcción.
Advertencias
Con hooks_raise=False, cada hook que falla emite un RuntimeWarning que nombra el tipo de hook y
el evento:
laya: hook Metrics.on_predict_end failed: connection reset
La advertencia se emite una vez por fallo, no una vez por definición de hook, así que un hook inestable bajo carga puede ser ruidoso. Si eso importa, agrega o limita la tasa dentro del hook.
Tiempos de espera
hooks_timeout limita cada llamada de hook en segundos. Un hook que sigue ejecutándose después del
límite se trata como un fallo de hook: TimeoutError cuando hooks_raise=True, un RuntimeWarning
cuando False. None (el valor predeterminado) significa sin límite.
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)
Se puede definir por instancia o sobrescribir por llamada en predict_batch, system_one,
Router.route, Router.predict y ONNXAgent.system_one. El valor debe ser positivo; 0 o un
número negativo lanza ValueError en el punto en que se define, en lugar de competir en un join
de longitud cero.
Un hook con tiempo límite se ejecuta en un hilo de trabajo sobre una copia del contexto contextvars
de quien llama, así que un id de solicitud o un span de tracing definido por quien llama es visible
para el hook.
Una advertencia honesta: Python no puede interrumpir un hilo, así que un hook que agota el tiempo
sigue ejecutándose en segundo plano. El tiempo límite acota cuánto espera la solicitud, no cuánto
vive el hook. Úsalo para mantener receptiva una solicitud servida, no para recuperar el trabajo.
Para un hook que puede colgarse, dale también su propio tiempo límite a la llamada subyacente (un
tiempo límite de socket o HTTP). Como el hilo no se puede recuperar, un hook que se cuelga en cada
llamada acumula hilos uno por llamada; dale a un hook que puede colgarse su propio límite en lugar
de confiar en que hooks_timeout lo detenga.
Para un hook asíncrono, la corrutina se ejecuta en el bucle de eventos; un tiempo límite en el lado que llama igualmente devuelve después del límite, y la corrutina sigue ejecutándose en el bucle.
El tiempo límite también libera el bloqueo de hooks_concurrent=False: dispatch espera al hook solo
hasta el límite y luego sigue, mientras que el hook que agotó el tiempo sigue ejecutándose fuera del
bloqueo. Así, el bloqueo serializa los hooks que terminan a tiempo, no todos los hooks que se
iniciaron alguna vez; un hook que se pasa de tiempo ya no bloquea a los que vienen detrás.
Elección de una política
| tipo de hook | recomendado | por qué |
|---|---|---|
| política / guardarraíl / ocultación de datos | hooks_raise=True |
una política que falla en silencio es un agujero de seguridad. |
| auditoría / registro | hooks_raise=True en pruebas, a menudo False en producción |
perder un registro de auditoría debería ser ruidoso, pero no necesariamente fatal. |
| métricas / tracing | hooks_raise=False |
la observabilidad no debe hacer fallar la solicitud. |
| lectura/escritura de caché | hooks_raise=True |
una caché rota debería aflorar, no fallar en silencio. |
Puedes combinar: instalar un guardarraíl con el valor predeterminado de la instancia y dar a un hook
de telemetría su propio try/except, o usar un hooks_raise distinto por llamada.
Véase también
- Ciclo de vida: la forma
try/except/finallyen contexto. - Patrones y antipatrones: errores comunes con el manejo de errores.