ドキュメント

エラー処理

エラー処理

フックはそれが観察するリクエストの内部で走るので、その失敗がどう扱われるかが重要になります。このページは正確な方針を示します。

2 つの方針

hooks_raise はフックが例外を投げたときの挙動を制御します。

hooks_raise 挙動
True(既定) フックの例外が呼び出しの外へ伝播します。
False フックは RuntimeWarning とともにスキップされ、呼び出しは続行します。

これはインスタンスごとに設定でき、呼び出しごとに上書きできます(hooks_raise= を predict_batch、system_one、Router.route、Router.predict に指定)。呼び出しごとの None は「インスタンスの値を使う」という意味です。

# 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 は Exception を捕捉します。Exception でないもの(BaseExceptionを参照)は、hooks_raise=False であっても決して握りつぶされません。

何かが失敗したときに何が走るか

predict のライフサイクルは try / except / finally で包まれているので、後始末のフックは失敗時にも走ります。

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)

イベントとランタイムごとの失敗行列:

イベント ランタイム 例外を投げたとき
on_predict_start Agent / Router hooks_raise=True:on_error と on_predict_end はそれでも走り、その後で例外が伝播します。False:警告して続行します(例外を投げる前の変更は残ります)。
推論 Agent / Router ctx.error が設定され、on_error が走り、on_predict_end が走り、例外が伝播します。
on_error Agent / Router 元の例外を決して覆い隠しません。__context__ として連鎖されます。
on_predict_end(成功経路) Agent / Router hooks_raise=True:伝播します(結果は計算済みなのに呼び出しは失敗します)。False:警告します。
on_predict_end(失敗経路) Agent / Router 元の例外を決して覆い隠しません。__context__ として連鎖されます。
on_route Router 直接伝播します。predict コンテキストはまだありません。
on_load Router 直接伝播します。チェックポイントは構築済みのまま常駐します。
on_evict Router 直接伝播します。チェックポイントはすでに解放されています。

知っておく価値のある帰結:

  • 失敗した on_load はモデルをキャッシュに残すので、次の load は on_load を再度発火せずにそれを返します。
  • 成功経路での失敗した on_predict_end は、推論が成功したにもかかわらず、呼び出し元が結果ではなく例外を受け取ることを意味します。純粋な副作用である end フックには hooks_raise=False を使ってください。

例外チェーン

別の例外がすでに伝播しているときにフックが失敗すると、元の例外が再送出され、フックの例外は __context__ として付加されます。根本原因が失われることはありません。

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

同じ規則が、失敗経路での失敗した on_predict_end にも当てはまります。

BaseException

dispatch は BaseException ではなく Exception を捕捉するので、KeyboardInterrupt と SystemExit は必ず伝播します。それらは predict ライフサイクルの except BaseException 分岐を依然として引き起こすので、プロセスが巻き戻る前に on_error と on_predict_end が走ります。割り込みのレイテンシが気になるなら、これらのフックを速くノンブロッキングに保ってください。

設定エラー

不正な設定は、推論の前に TypeError で即座に失敗します。

ケース 発生箇所 例
インスタンスではなくクラス 構築時 hooks=[MyHook]
ライフサイクルメソッドがない 構築時 hooks=[object()]
callable でないイベント 構築時 on_predict_start = 5
callable でない便宜フック 構築時 on_predict_start=123
hooks= に素の callable 構築時 hooks=[lambda ctx: None]

呼び出しごとのフックは呼び出し時点で検証されるので、不正な呼び出しごとのフックは構築時ではなく predict/system_one から TypeError を投げます。

警告

hooks_raise=False では、失敗したフックごとに、フックの型とイベントを名指しする RuntimeWarning が 1 つ出ます。

laya: hook Metrics.on_predict_end failed: connection reset

警告はフック定義ごとではなく失敗ごとに 1 回出るので、負荷時に不安定なフックはうるさくなりえます。それが問題なら、フックの内部で集約するかレート制限してください。

タイムアウト

hooks_timeout は各フック呼び出しを秒単位で制限します。制限後も走っているフックはフックの失敗として扱われます。hooks_raise=True なら TimeoutError、False なら RuntimeWarning です。None(既定)は制限なしを意味します。

laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)

インスタンスごとに設定でき、predict_batch、system_one、Router.route、Router.predict、ONNXAgent.system_one で呼び出しごとに上書きもできます。値は正でなければなりません。0 や負の数は、長さゼロの join で競合するのではなく、設定した時点で ValueError を投げます。

時間制限付きのフックは、呼び出し元の contextvars コンテキストのコピー内の worker スレッドで走るので、呼び出し元が設定したリクエスト id やトレーシング span がフックから見えます。

正直な注意点が 1 つあります。Python はスレッドを中断できないので、タイムアウトしたフックはバックグラウンドで走り続けます。タイムアウトが制限するのはリクエストが待つ時間であり、フックが生きる時間ではありません。配信中のリクエストを応答性よく保つために使い、作業を取り戻すためには使わないでください。ハングしうるフックには、下位の呼び出しにも独自のタイムアウト(ソケットや HTTP のタイムアウト)を与えてください。スレッドは回収できないので、毎回ハングするフックは呼び出しごとにスレッドを増やします。ハングしうるフックには hooks_timeout で止めることを当てにせず、独自の制限を与えてください。

非同期フックでは、コルーチンはイベントループ上で走ります。呼び出し側のタイムアウトは制限後に返りますが、コルーチンはループ上で走り続けます。

タイムアウトは hooks_concurrent=False のロックも解放します。dispatch は制限までしかフックを待たず、その後は進み、タイムアウトしたフックはロックの外で走り続けます。つまりロックが直列化するのは間に合って終わるフックであり、開始されたすべてのフックではありません。超過したフックは後ろのフックをブロックしなくなります。

方針の選び方

フックの種類 推奨 理由
ポリシー / ガードレール / 秘匿化 hooks_raise=True 黙って失敗するポリシーはセキュリティホールです。
監査 / ログ テストでは hooks_raise=True、本番ではしばしば False 監査記録の喪失は大声であるべきですが、必ずしも致命的である必要はありません。
メトリクス / トレーシング hooks_raise=False 可観測性がリクエストを失敗させてはいけません。
キャッシュの読み書き hooks_raise=True 壊れたキャッシュは表面化すべきで、黙ってミスしてはいけません。

混在させられます。ガードレールはインスタンス既定で入れ、テレメトリフックには独自の try/except を与える、あるいは呼び出しごとに別々の hooks_raise を使えます。

関連項目