Документация

Hooks предсказаний

Hooks позволяют наблюдать за каждым решением Laya или формировать его, не делая форк.

Это шов расширения для всего, что нужно каждому реальному развёртыванию: журналирование аудита, редактирование PII до инференса, кэширование, метрики, gating по уверенности, переопределения маршрутизации и пересылка решения во внешний сервис. Они opt-in: без настроенных hooks поведение Agent, Router и ONNXAgent не меняется.

Они нужны не только для прямых вызовов. Каждый runnable из LangChain и LangGraph принимает те же пять аргументов на вызов, поэтому hook можно привязать к одному узлу графа, а не ко всему агенту.

Эта папка — полный справочник. Начните здесь, затем переходите на нужную страницу:

страница что в ней
Справочник API каждый класс, поле, параметр и значение по умолчанию
Жизненный цикл точно, когда запускается каждый hook, со схемами
Ошибки hooks_raise, on_error, связывание исключений, матрица сбоев
Паттерны и антипаттерны что делать, чего избегать и почему
Примеры готовые рецепты для каждого случая использования
Трассировка run_id, корреляция span’ов, OpenTelemetry

Быстрый старт

import laya

def log(ctx):
    print(ctx.model, ctx.results[0]["answers"], ctx.elapsed_ms)

agent = laya.load("convaiinnovations/laya", on_predict_end=log)
agent.system_one("I was charged twice.", {"urgent": {"type": "noul", "instructions": "Urgent?"}})

Объект может реализовать любое подмножество событий жизненного цикла:

class Audit:
    def on_predict_start(self, ctx):
        print("start", ctx.run_id)

    def on_predict_end(self, ctx):
        print("end", ctx.run_id, ctx.usage, ctx.elapsed_ms)

    def on_error(self, ctx):
        print("failed", ctx.run_id, ctx.error)

laya.load("convaiinnovations/laya", hooks=[Audit()])

Hooks также можно добавить позже или ограничить блоком:

agent.add_hook(tracer)               # attach at runtime
with agent.hooks_installed(debug):   # installed for the block, removed on exit
    agent.system_one(state, questions)

Смотрите регистрацию во время выполнения. Для hook, который должен применяться везде, без прокидывания через каждый вызов, зарегистрируйте его один раз с помощью общих настроек процесса:

from laya import hooks

hooks.set_default_hooks(hooks=[Tracer()])

Мысленная модель

Есть три идеи.

  1. Hook — это вызываемый объект или объект. Обычная функция удобна для одного события; объект — для нескольких. Оба передаются в hooks= / on_predict_start= / on_predict_end=.

  2. Все hooks одного вызова разделяют один изменяемый PredictContext. Он несёт состояния, вопросы, результаты, решение маршрутизации, имя модели, использование, тайминги и любую ошибку. Вызов может нести много состояний сразу (predict_batch), поэтому hook, который намерен покрыть каждое решение, должен перебирать ctx.states и ctx.results; ctx.usage и ctx.elapsed_ms — это итоги по вызову. Поскольку контекст изменяем, hook может формировать вызов, а не только наблюдать: редактировать состояние, переписывать вопросы, заменять результат или пропускать инференс с кэшированным ответом.

  3. Есть две области видимости. Hooks Agent оборачивают прямой проход; hooks Router оборачивают маршрутизацию плюс инференс и могут также видеть жизненный цикл модели (on_route, on_load, on_evict). Это повторяет разделение «hooks запуска» и «hooks агента» в других агентных фреймворках.

                             Router.predict(state, questions)
   ┌──────────────────────────────────────────────────────────────────────────┐
   │  route()                                                                 │
   │    ├─ detect language / workflow                                         │
   │    └─ on_route          ctx.decision  (a hook may replace it)            │
   │                                                                          │
   │  load(decision.model)                                                    │
   │    ├─ build checkpoint on first use ──► on_load    ctx.model, ctx.agent  │
   │    └─ evict LRU checkpoint ───────────► on_evict   ctx.model             │
   │                                                                          │
   │  on_predict_start       ctx.states, ctx.questions, ctx.decision          │
   │    │                                                                     │
   │    ├── ctx.skip(results)? ──► skip the forward pass                      │
   │    │                                                                     │
   │    └── Agent.system_one(...)  ──►  Agent-level hooks run here            │
   │           on_predict_start  ─►  forward  ─►  on_predict_end              │
   │                                                                          │
   │  result["routing"] = decision                                            │
   │  on_predict_end         ctx.results, ctx.usage, ctx.elapsed_ms           │
   └──────────────────────────────────────────────────────────────────────────┘
                 any failure on the way ──► on_error, then on_predict_end

Область видимости одним взглядом

Agent / ONNXAgent Router
on_predict_start да да
on_predict_end да да
on_error да да
on_route нет да
on_load нет да
on_evict нет да

laya.serve и сервер MCP вызывают Router.predict, поэтому hooks Router срабатывают для них автоматически. Hooks Agent срабатывают всегда, когда Router запускает присоединённого или встроенного агента.

Совместимость

  • Без настроенных hooks поведение не меняется. Несконфигурированный путь покрыт регрессионными тестами.
  • Все параметры hooks — это именованные аргументы со значениями по умолчанию, поэтому существующие вызовы продолжают работать.
  • laya/hooks.py — чистый Python: из-за него import laya не тянет torch.
  • Hooks по умолчанию синхронны. Событие async def можно обернуть в AsyncHook или передать как обычный асинхронный вызываемый объект, и оно выполнится до конца за вас.
  • hooks_timeout ограничивает медленный hook, чтобы он не мог подвесить обслуживаемый запрос.
  • Держите hooks быстрыми и неблокирующими; о последствиях для laya.serve смотрите ошибки и паттерны.

Смотрите также