Справочник по API
Всё на этой странице можно импортировать из laya (общие имена) или из laya.hooks (вся поверхность).
from laya import PredictContext, PredictHook, Hook
from laya.hooks import HOOK_EVENTS, normalise_hooks, dispatch, aggregate_usage
- PredictContext
- Протокол hook
- Удобные типы
- Поверхность конфигурации
- Payload событий
- Валидация
- Продвинутые helpers
PredictContext
PredictContext создаётся один раз на каждый публичный вызов и передаётся каждому hook этого вызова.
Router.predict_batch — единственное исключение: он создаёт по одному на запрос, как это сделал бы вызов
Router.predict для этого запроса, поэтому его hooks уровня Router срабатывают один раз на запрос с одним
состоянием каждый. Он изменяемый: hooks могут переписывать states, questions и results, а
on_route может переписывать decision. Он использует равенство по идентичности (eq=False), поэтому
контекст хэшируем и два контекста никогда не равны.
@dataclass(eq=False)
class PredictContext:
states: List[Any]
questions: Dict[str, Any]
run_id: str = <uuid4 hex>
results: Optional[List[Dict[str, Any]]] = None
decision: Optional[Dict[str, Any]] = None
model: Optional[str] = None
agent: Any = None
router: Any = None
max_len: Optional[int] = None
head_max_len: Optional[int] = None
usage: Optional[Dict[str, int]] = None
started_at: float = <perf_counter()>
elapsed_ms: Optional[float] = None
error: Optional[BaseException] = None
| поле | тип | когда задаётся | изменяемый | значение |
|---|---|---|---|---|
states |
list |
всегда | да (начало) | состояния этого вызова. system_one/Router.predict передаёт одно; Agent.predict_batch передаёт много; Router.predict_batch передаёт одно на запрос. Hook начала может заменить список. |
questions |
dict |
всегда | да (начало) | вопросы. Hook начала может заменить словарь. |
run_id |
str |
всегда | нет | уникальный id, общий для каждого hook этого вызова. Используйте его для корреляции событий и спанов. |
results |
list | None |
конец (и при skip) | да (конец) | словари результатов по состояниям, каждый формы возвращаемого значения system_one. None, пока не завершится инференс. |
decision |
dict | None |
только Router | да (route) | RouteDecision (это dict), выбравший чекпойнт. |
model |
str | None |
всегда | нет | id чекпойнта: Agent.model_id для Agent, разрешённый псевдоним (например, "english") для Router. |
agent |
Agent | ONNXAgent | None |
события predict | нет | среда выполнения, отвечающая на вызов. |
router |
Router | None |
события Router | нет | Router, когда он задействован. |
max_len |
int | None |
всегда | да (начало) | бюджет токенов на вызов для encoder. None использует конфиг agent. |
head_max_len |
int | None |
всегда | да (начало) | бюджет токенов на вызов для головы вопроса. None использует конфиг agent. |
usage |
dict | None |
конец | да (конец) | {"input_tokens", "output_tokens"}, просуммированные по состояниям вызова. |
started_at |
float |
всегда | нет | time.perf_counter() когда начался вызов. |
elapsed_ms |
float | None |
конец | нет | реальное время всего вызова, в миллисекундах. |
error |
BaseException | None |
путь сбоя | нет | исключение, заданное до on_error и on_predict_end. |
PredictContext.skip(results)
Закорачивает инференс. Вызванный из on_predict_start, он задаёт ctx.results, так что прямой проход
пропускается; on_predict_end всё равно выполняется, и переданные результаты возвращаются.
Ключ должен покрывать всё, от чего зависит ответ, а hook — всё, что несёт вызов: hooks срабатывают один
раз на вызов, а predict_batch вызывает их сразу со всеми состояниями.
CACHE = {}
def key(ctx, index):
# Not sort_keys=True: criteria order is positional, so two orders are two questions,
# and the checkpoint and token budget change the answer too.
payload = json.dumps([ctx.states[index], ctx.questions, ctx.model,
ctx.max_len, ctx.head_max_len], default=str)
return hashlib.sha256(payload.encode()).hexdigest()
def cache_read(ctx):
hits = [CACHE.get(key(ctx, i)) for i in range(len(ctx.states))]
if all(hit is not None for hit in hits):
ctx.skip(hits) # one entry per state, same shape as predict_batch's return
def cache_write(ctx):
for i, result in enumerate(ctx.results or []):
CACHE[key(ctx, i)] = result
laya.load("convaiinnovations/laya", on_predict_start=cache_read, on_predict_end=cache_write)
tests/test_hooks_api.py выполняет этот блок, examples/hooks/cache.py и блоки кэширования из
docs/hooks/patterns.md и docs/hooks/examples.md, и проверяет все четыре ключа одинаково, поэтому
страница не может учить ключу, от которого пример уже отошёл.
На Router в пропущенный payload добавляется ключ routing (не перезаписывая уже имеющийся), поэтому
Router.predict сохраняет свою документированную форму возврата.
Протокол hook
Hook — это typing.Protocol. Реализуйте любое подмножество методов; остальные пропускаются.
class Hook(Protocol):
def on_predict_start(self, ctx: PredictContext) -> None: ...
def on_predict_end(self, ctx: PredictContext) -> None: ...
def on_route(self, ctx: PredictContext) -> None: ...
def on_load(self, ctx: PredictContext) -> None: ...
def on_evict(self, ctx: PredictContext) -> None: ...
def on_error(self, ctx: PredictContext) -> None: ...
| событие | где | выполняется | может изменить |
|---|---|---|---|
on_predict_start |
Agent, Router | до токенизации/прямого прохода | states, questions или skip() |
on_predict_end |
Agent, Router | после появления результатов, при успехе или сбое | results |
on_route |
Router | после определения, до загрузки | decision |
on_load |
Router | после сборки чекпойнта | ничего (наблюдение) |
on_evict |
Router | после освобождения чекпойнта | ничего (наблюдение) |
on_error |
Agent, Router | когда вызов predict падает | ничего (наблюдение) |
Hook может свободно определять дополнительные атрибуты и методы; учитываются только шесть имён событий. Если hook определяет одно из шести как невызываемое, конфигурация падает сразу (см. Валидация).
BaseHook
BaseHook — конкретный двойник протокола: класс с пустым телом для каждого события. Создайте подкласс и
переопределите только нужные события.
from laya import BaseHook
class Audit(BaseHook):
def on_predict_end(self, ctx):
ship(ctx.run_id, ctx.results)
Hook лучше, когда нужна структурная типизация (любой объект с нужными методами); BaseHook лучше,
когда нужна явная база для наследования и вызова super().
Удобные типы
PredictHook = Callable[[PredictContext], None]
PredictHook — это тип обычного callable-объекта, используемого с on_predict_start= /
on_predict_end=. Передайте один callable-объект или последовательность; каждый оборачивается в
минимальный hook.
Регистрация во время выполнения
Каждая среда выполнения подмешивает HookRegistry, поэтому hooks можно добавлять, удалять или
ограничивать по области после создания. Изменение потокобезопасно; вызов читает снимок списка, поэтому
добавление или удаление hook никогда не мешает выполняющемуся вызову.
agent.add_hook(tracer) # one hook or a sequence; returns self for chaining
agent.remove_hook(tracer) # by identity; True if it was installed
with agent.hooks_installed(debug): # installed for the block, removed on exit
agent.system_one(state, questions)
add_hook принимает те же объекты, что и hooks= (не обычные callable-объекты). hooks_installed
принимает любое число объектов hook или последовательностей и при выходе восстанавливает предыдущий
список, в том числе когда блок вызывает исключение.
Значения по умолчанию для всего процесса
laya.hooks хранит небольшой реестр для всего процесса, поэтому tracer, hook метрик или разметчик
tenant’ов не нужно протягивать через каждый Agent и Router. Значения по умолчанию выполняются
первыми, затем hooks, установленные на экземпляре, затем hooks на вызов.
from laya import hooks
hooks.set_default_hooks(hooks=[Tracer()]) # replaces the set, accepts the hooks= arguments
hooks.add_default_hook(Metrics()) # appends
hooks.clear_default_hooks() # removes everything
hooks.default_hooks() # a copy of the current list
hooks.compose_hooks(agent.hooks) # defaults + installed (advanced)
Значения по умолчанию применяются к каждому событию, включая события жизненного цикла Router on_load и
on_evict. Реестр читается во время вызова, поэтому hooks, заданные после создания Agent или Router,
всё равно применяются. Нет отказа для отдельного экземпляра; вызовите clear_default_hooks(), чтобы
отключить набор для всего процесса.
Асинхронные hooks
Событие может быть корутиной. Оберните hook в AsyncHook, и его методы async def выполняются до
завершения в синхронном ядре:
from laya import AsyncHook
class Remote:
async def on_predict_end(self, ctx):
await ship(ctx.results)
agent = laya.load("convaiinnovations/laya", hooks=[AsyncHook(Remote())])
Обычный асинхронный callable-объект, переданный в on_predict_start= / on_predict_end=, тоже
работает, потому что dispatch выполняет любой awaitable, который вернёт hook.
Где выполняется корутина:
- Если у вызывающего потока нет запущенного цикла, она выполняется через
asyncio.run. - Если он уже есть (вызывающий внутри асинхронной функции), она выполняется в выделенном фоновом цикле,
поэтому вызывающий поток может блокироваться без взаимной блокировки. Передайте
AsyncHook(hook, loop=...), чтобы направить её в конкретный цикл; он должен быть запущен и не должен быть собственным циклом вызывающего потока. Проверяются оба: остановленный цикл и собственный цикл вызывающего каждый вызываетValueErrorвместо бесконечной блокировки.
Hook без async-методов не затрагивается.
Поверхность конфигурации
Каждая точка входа принимает одни и те же параметры hook. hooks принимает объект или последовательность
объектов; on_predict_start / on_predict_end принимают callable-объект или последовательность.
| параметр | тип | по умолчанию | значение |
|---|---|---|---|
hooks |
Hook | Sequence[Hook] | None |
None |
hooks жизненного цикла (любое из шести событий). |
on_predict_start |
PredictHook | Sequence[PredictHook] | None |
None |
удобные callable-объекты для одного события. |
on_predict_end |
PredictHook | Sequence[PredictHook] | None |
None |
удобные callable-объекты для одного события. |
hooks_raise |
bool |
True |
True: исключение hook распространяется. False: предупредить и продолжить. |
hooks_concurrent |
bool |
True |
False: диспетчеризовать hooks под блокировкой, по одному. |
hooks_timeout |
float | None |
None |
лимит времени на hook в секундах; None означает без лимита. |
Agent
Agent(
model_id_or_path="convaiinnovations/laya",
device=None, token=None, subfolder=None, fast=False, compile=False,
hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True, hooks_timeout=None,
)
load(..., hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True, hooks_timeout=None)
agent.predict_batch(states, questions, batch_size=None,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None, sort_by_length=False)
agent.system_one(state, questions,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None)
agent.predict_long(state, questions, window=None, stride=None, aggregate="auto",
batch_size=None, lang=None,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None)
agent.predict(...) # alias of system_one
-
hooks_raiseиhooks_timeoutв методе на вызов по умолчанию равныNone, что означает «использовать значение экземпляра». -
hooks_concurrentсуществует только на уровне экземпляра. -
В
predict_longhooks оборачивают инференс, который отвечает на состояние; для документа, требующего нескольких окон, это единственный общийpredict_batchповерх них:on_predict_startсрабатывает один раз, иctx.statesсодержит декодированные тексты окон в порядке сканирования, а не состояние вызывающего, которое было токенизировано, чтобы их создать.statesизменяем из hook начала, поэтому сканирование, доходящее до инференса, не обязано быть разбиением, которое вычислилpredict_long. Меняется то, что может утверждать ответ:что сделал hook начала usage["windows"]answer["window"]ответил через ctx.skip([result])0отсутствует — ни одно окно его не оценивало оставил сканирование как было построено Nприсутствует — index,token_start/token_endназывают решающий участокзаменил сканирование любым способом состояния, которые были оценены отсутствует — смещения описывают окна predict_long, а не текст, который был оценёнusage["windows"]— это сумма по каждому пути, включая тот, где состояние уже помещалось в одно окно, поэтому кэшированный ответ никогда не читается как окно, которое прочитала модель.
Router
Router(
models=None, device=None, token=None, max_loaded=2, default="english",
auto_task_detection=False, standalone_repos=False, preload=False, lang_guess=None,
hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True,
)
router.route(state, questions=None, model=None, task=None, lang=None, lang_guess=None,
hooks=None, hooks_raise=None)
router.predict(state, questions, model=None, task=None, lang=None, lang_guess=None,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None)
router.predict_batch(requests, batch_size=None, hooks_timeout=None, min_confidence=None,
sort_by_length=False, hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=None)
router.system_one(...) # alias of predict
router.load(name) # builds on first use; fires on_load
router.preload(names=None) # builds several; fires on_load per build
router.unload(name=None) # frees one or all; fires on_evict
router.attach(name, agent) # registers an existing agent; does not fire on_load
router.loaded # list of resident checkpoint names
hooks=на вызов вroute,route_batch,predictиpredict_batchприменяется ко всему вызову, включаяon_route. Вpredict_batchсписок составляется так же, как вpredict(сначала установленные hooks, затем список на вызов;Noneи[]ничего не добавляют), и выполняется один раз на запрос.route()публичный: его вызов диспетчеризуетon_routeс установленными hooks плюс любыеhooksна вызов.
ONNXAgent
ONNXAgent(model_id_or_path, onnx_path="laya.onnx", subfolder=None,
hooks=None, on_predict_start=None, on_predict_end=None,
hooks_raise=True, hooks_concurrent=True)
onnx_agent.system_one(state, questions,
hooks=None, on_predict_start=None, on_predict_end=None, hooks_raise=None,
hooks_timeout=None, max_len=None, head_max_len=None)
onnx_agent.predict(...) # alias of system_one
У ONNXAgent нет Router, поэтому он предоставляет только события уровня predict.
Payload событий
Какие поля заполнены, по событиям и средам выполнения:
| событие | среда выполнения | states |
questions |
decision |
model |
agent |
router |
results |
usage |
elapsed_ms |
error |
|---|---|---|---|---|---|---|---|---|---|---|---|
on_predict_start |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | – | – | – | – |
on_predict_start |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | – | – | – | – |
on_predict_end |
Agent | ✓ | ✓ | – | ✓ | ✓ | – | ✓ | при успехе | ✓ | при сбое |
on_predict_end |
Router | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | при успехе | ✓ | при сбое |
on_error |
обе | ✓ | ✓ | ✓ (Router) | ✓ | ✓ | ✓ (Router) | – | – | – | ✓ |
on_route |
Router | ✓ | ✓ | ✓ | – | – | ✓ | – | – | – | – |
on_load |
Router | [] |
{} |
– | ✓ | ✓ | ✓ | – | – | – | – |
on_evict |
Router | [] |
{} |
– | ✓ | – | ✓ | – | – | – | – |
Детали по времени:
on_predict_endвидитresultsна пути успеха. На пути сбояresultsравноNone, если hook начала не задал их черезskip(), иusageпоэтому тожеNone(оно выводится изresults);elapsed_msвсегда задано.on_errorвыполняется до блокаfinally, который вычисляетelapsed_msиusage, поэтому там оба равныNone. Читайте время и usage изon_predict_endвместо этого.run_idвсегда заполнено.
Валидация
Конфигурация проверяется, когда hooks нормализуются, что происходит при создании для установленных hooks и
во время вызова для hooks на вызов. Следующее вызывает TypeError:
| случай | сообщение |
|---|---|
| передаётся класс вместо экземпляра | hooks entries must be instances, not classes; ... |
| объект не реализует ни одного из шести событий | hooks entries must implement at least one of ... |
| атрибут события невызываемый | hooks entry X.on_predict_start must be callable, got int |
on_predict_start= / on_predict_end= невызываемый |
on_predict_start must be callable, got int |
hooks= не принимает обычные callable-объекты, потому что голый callable-объект не говорит, для какого
события он. Для них используйте on_predict_start= / on_predict_end=.
Продвинутые helpers
Они используются внутри и стабильны, но большинству пользователей не нужны.
HOOK_EVENTS # tuple of the six event names, in dispatch order
normalise_hooks(hooks=None, on_predict_start=None, on_predict_end=None) -> list
dispatch(hooks, event, ctx, *, raise_errors=True, lock=None) -> None
aggregate_usage(results) -> {"input_tokens": int, "output_tokens": int}
dispatch(hooks, event, ctx, *, raise_errors=True, lock=None, timeout=None)
run_coroutine_sync(coro, loop=None)
normalise_hooks разворачивает объект/последовательность hooks и два callable-объекта в один
упорядоченный список. dispatch вызывает event на каждом hook, который его реализует, применяя политику
raise, блокировку и тайм-аут, и выполняет результат hook, если он awaitable. run_coroutine_sync
выполняет awaitable до завершения из синхронного кода, в цикле вызывающего, если он свободен, или в
фоновом цикле, если у вызывающего уже есть свой. aggregate_usage суммирует блоки usage по состояниям.
from laya.hooks import normalise_hooks, dispatch, PredictContext
hooks = normalise_hooks(on_predict_start=[log, redact])
ctx = PredictContext(states=["..."], questions={...})
dispatch(hooks, "on_predict_start", ctx)
См. также
- Жизненный цикл: когда выполняется каждое событие, со схемами потока.
- Ошибки: матрица сбоев и правила цепочки.
- Паттерны и антипаттерны: как правильно структурировать hooks.
- Примеры: рецепты для каждого случая использования.