Gestion des erreurs
Les hooks tournent à l’intérieur de la requête qu’ils observent, donc la façon dont leurs échecs sont traités compte. Cette page est la politique exacte.
- Les deux politiques
- Ce qui tourne quand quelque chose échoue
- Enchaînement des exceptions
- BaseException
- Erreurs de configuration
- Avertissements
- Choisir une politique
Les deux politiques
hooks_raise contrôle ce qui se passe quand un hook lève une erreur.
hooks_raise |
comportement |
|---|---|
True (défaut) |
l’exception du hook se propage hors de l’appel. |
False |
le hook est ignoré avec un RuntimeWarning et l’appel continue. |
Il se définit par instance et peut être surchargé par appel (hooks_raise= sur predict_batch,
system_one, Router.route, Router.predict). None par appel signifie « utilise la valeur de
l’instance ».
# 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 attrape Exception. Tout ce qui n’est pas une Exception (voir
BaseException) n’est jamais avalé, même avec hooks_raise=False.
Ce qui tourne quand quelque chose échoue
Le cycle de vie de la prédiction est enveloppé dans try / except / finally, donc les hooks de
nettoyage tournent en cas d’échec.
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)
Matrice de défaillance, par événement et par runtime :
| événement | runtime | s’il lève |
|---|---|---|
on_predict_start |
Agent / Router | hooks_raise=True : on_error et on_predict_end tournent quand même, puis l’exception se propage. False : avertir et continuer (les mutations faites avant la levée restent). |
| inférence | Agent / Router | ctx.error défini, on_error tourne, on_predict_end tourne, l’exception se propage. |
on_error |
Agent / Router | ne masque jamais l’exception d’origine ; enchaîné comme __context__. |
on_predict_end (chemin du succès) |
Agent / Router | hooks_raise=True : se propage (le résultat est calculé mais l’appel échoue). False : avertir. |
on_predict_end (chemin d’échec) |
Agent / Router | ne masque jamais l’exception d’origine ; enchaîné comme __context__. |
on_route |
Router | se propage directement ; il n’y a pas encore de contexte de prédiction. |
on_load |
Router | se propage directement ; le checkpoint reste construit et résident. |
on_evict |
Router | se propage directement ; le checkpoint est déjà libéré. |
Conséquences à connaître :
- Un
on_loadqui échoue laisse le modèle en cache, donc le prochainloadle renvoie sans redéclencheron_load. - Un
on_predict_endqui échoue sur le chemin du succès signifie que l’appelant reçoit une exception au lieu d’un résultat, même si l’inférence a réussi. Utilisehooks_raise=Falsepour les hooks de fin qui sont de purs effets de bord.
Enchaînement des exceptions
Quand un hook échoue alors qu’une autre exception est déjà en train de se propager, l’exception
d’origine est relancée et celle du hook est attachée comme __context__. La cause racine n’est jamais
perdue.
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 même règle s’applique à un on_predict_end qui échoue sur le chemin d’échec.
BaseException
dispatch attrape Exception, pas BaseException, donc KeyboardInterrupt et SystemExit se
propagent toujours. Ils déclenchent quand même la branche except BaseException du cycle de vie de la
prédiction, ce qui signifie que on_error et on_predict_end tournent avant que le processus ne se
déroule. Garde ces hooks rapides et non bloquants si la latence d’interruption t’importe.
Erreurs de configuration
Une mauvaise configuration échoue immédiatement avec TypeError, avant toute inférence :
| cas | levé à | exemple |
|---|---|---|
| classe au lieu d’instance | construction | hooks=[MyHook] |
| aucune méthode de cycle de vie | construction | hooks=[object()] |
| événement non appelable | construction | on_predict_start = 5 |
| hook de commodité non appelable | construction | on_predict_start=123 |
appelable simple dans hooks= |
construction | hooks=[lambda ctx: None] |
Les hooks par appel sont validés quand l’appel est fait, donc un mauvais hook par appel lève
TypeError depuis predict/system_one plutôt qu’à la construction.
Avertissements
Avec hooks_raise=False, chaque hook qui échoue émet un RuntimeWarning nommant le type de hook et
l’événement :
laya: hook Metrics.on_predict_end failed: connection reset
L’avertissement est émis une fois par échec, pas une fois par définition de hook, donc un hook instable sous charge peut être bruyant. Agrège ou limite le débit à l’intérieur du hook si cela compte.
Délais d’attente
hooks_timeout borne chaque appel de hook en secondes. Un hook encore en cours après la limite est
traité comme un échec de hook : TimeoutError quand hooks_raise=True, un RuntimeWarning quand
False. None (le défaut) signifie aucune limite.
laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)
Il peut être défini par instance ou surchargé par appel sur predict_batch, system_one,
Router.route, Router.predict et ONNXAgent.system_one. La valeur doit être positive ; 0 ou un
nombre négatif lève ValueError à l’endroit où il est défini, plutôt que de courir sur un join de
longueur nulle.
Un hook temporisé tourne sur un thread de travail dans une copie du contexte contextvars de
l’appelant, donc un id de requête ou un span de traçage défini par l’appelant est visible par le hook.
Une mise en garde honnête : Python ne peut pas interrompre un thread, donc un hook expiré continue de
tourner en arrière-plan. Le timeout borne combien de temps la requête attend, pas combien de temps le
hook vit. Utilise-le pour garder une requête servie réactive, pas pour récupérer le travail. Pour un
hook qui peut se bloquer, donne aussi à l’appel sous-jacent son propre timeout (un timeout de socket
ou HTTP). Comme le thread ne peut pas être récupéré, un hook qui se bloque à chaque appel fait croître
les threads d’un par appel ; donne à un hook qui peut se bloquer sa propre borne plutôt que de compter
sur hooks_timeout pour l’arrêter.
Pour un hook async, la coroutine tourne sur la boucle d’événements ; un timeout côté appelant revient quand même après la limite, et la coroutine continue de tourner sur la boucle.
Le timeout libère aussi le verrou hooks_concurrent=False : dispatch attend le hook seulement jusqu’à
la limite, puis passe à la suite, tandis que le hook expiré continue de tourner hors du verrou. Donc le
verrou sérialise les hooks qui finissent à temps, pas tous les hooks qui ont été démarrés ; un hook qui
dépasse ne bloque plus ceux derrière lui.
Choisir une politique
| type de hook | recommandé | pourquoi |
|---|---|---|
| politique / garde-fou / masquage | hooks_raise=True |
une politique qui échoue silencieusement est un trou de sécurité. |
| audit / journalisation | hooks_raise=True en test, souvent False en production |
perdre un enregistrement d’audit doit être bruyant, mais pas forcément fatal. |
| métriques / traçage | hooks_raise=False |
l’observabilité ne doit pas faire échouer la requête. |
| lecture/écriture de cache | hooks_raise=True |
un cache cassé doit se voir, pas manquer silencieusement. |
Tu peux mélanger : installe un garde-fou avec le défaut de l’instance et donne à un hook de télémétrie
son propre try/except, ou utilise un hooks_raise distinct par appel.
Voir aussi
- Cycle de vie : la forme
try/except/finallyen contexte. - Motifs et anti-motifs : erreurs courantes de gestion d’erreurs.