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

Router

laya.Router определяет язык каждого состояния и отправляет запрос в соответствующий чекпойнт, загружая чекпойнты при первом использовании.

Имена, типы, значения по умолчанию и код остаются на английском; остальное переведено (ещё не переведённые записи показываются в английском оригинале).

Router

Router(
    models: Optional[Dict[str, str]] = None,
    device: Optional[str] = None,
    token: Optional[str] = None,
    revision: Optional[str] = None,
    revisions: Optional[Dict[str, Optional[str]]] = None,
    max_loaded: int = 2,
    default: str = "english",
    auto_task_detection: bool = False,
    standalone_repos: bool = False,
    preload: bool = False,
    lang_guess: Optional[Any] = None,
    hooks=None,
    on_predict_start=None,
    on_predict_end=None,
    hooks_raise: bool = True,
    hooks_concurrent: bool = True,
    hooks_timeout: Optional[float] = None,
    agent_kwargs: Optional[Dict[str, Any]] = None,
    sha256_digests: Optional[Dict[str, Optional[Dict[str, str]]]] = None,
)

Базовые классы: HookRegistry

Лениво загружает чекпойнты Laya и отправляет каждый запрос к нужному из них.

from laya import Router

r = Router()
r.predict({"message": "Mein Konto wurde zweimal belastet"}, questions)   # -> multilingual
r.predict({"message": "I was charged twice"}, questions)                 # -> english
r.predict(state, questions, model="typed-decisions")                     # explicit

Модели скачиваются и строятся при первом использовании. max_loaded ограничивает, сколько остаётся резидентно (вытесняется использованная реже всего), потому что все три вместе — ~1.16B параметров.

Значение по умолчанию — 2, потому что автоматическая маршрутизация выбирает только между english и multilingual: предел в один перестраивает чекпойнт, который только что вытеснил, при каждом переключении языка, а это секунды на запрос именно на том трафике, для которого Router и существует. Трафик, который всегда видит только один язык, никогда не строит второй чекпойнт, так что значение по умолчанию ему ничего не стоит. Опустите его до 1 для хоста с ограниченной памятью и поднимите до 3 (или выполните предзагрузку), когда auto_task_detection, явный model= или явный task= также могут достичь typed-decisions.

Для сервера или демо лучше предзагрузка: холодная загрузка стоит секунды, тогда как определение стоит микросекунды, так что даже значение по умолчанию всё равно платит загрузку при первом появлении языка.

r = Router(preload=True)                    # all three resident, routing is free
r = Router(preload=True, device="cuda")
r.preload(["english", "multilingual"])      # or just the two you serve

Ревизии Hub — opt-in. revision применяет один commit ко всем моделям; revisions={"english": "...", "multilingual": "..."} переопределяет это по модели, что полезно, когда отдельные репозитории проверялись на разных коммитах. Без того или другого используются обычное значение по умолчанию huggingface_hub и существующий офлайн-кэш.

Запись revisions, равная None или пустая, означает "нет переопределения для этой модели", поэтому модель наследует revision -- форма, которую записывает {"english": os.environ.get("EN_SHA")}, когда переменная не задана, что не должно стоить вызывающему той привязки, которую он запросил. Ничто в revisions не может отменить привязку одной модели, пока revision привязывает остальные; оставьте revision незаданным и назовите модели, которые хотите привязать.

Пустой revision читается так же, и это намеренное изменение в том, что Router передаёт дальше: Router(revision=" ") раньше доходил до resolve_revision, где истинная, но пустая строка подавляла fallback LAYA_REVISION и оставляла значение по умолчанию huggingface_hub, а теперь отбрасывается до этого, так что Router, настроенный с пробелами, ведёт себя как настроенный ни с чем, и применяется $LAYA_REVISION. Это и должно означать "эта строка конфигурации никогда не заполнялась", если revision и запись revisions должны читаться одинаково.

Это одно из двух мест, где более слабый источник оказывается впереди явного аргумента. Другое — запись дайджеста по чекпойнту из LAYA_SHA256_DIGESTS, которая побеждает expected_sha256, переданный через agent_kwargs; см. docstring класса.

Всё остальное, что принимает laya.Agent, достижимо через agent_kwargs, которые сливаются в каждый чекпойнт, который строит Router:

Router(agent_kwargs={"lang_temperatures": {"de": {"temperature": [1.0, 1.4, 2.0]}}})
Router(agent_kwargs={"expected_sha256": {"model.safetensors": "a3f1..."}})
Router(agent_kwargs={"fast": True})

expected_sha256 там привязывает одни и те же файлы на каждом чекпойнте, что и нужно одному резидентному чекпойнту или общему tokenizer.json. Он никогда не отбрасывается целиком собственными дайджестами чекпойнта: когда у того тоже есть запись, из sha256_digests или из по-чекпойнтного LAYA_SHA256_DIGESTS, две карты сливаются файл за файлом, так что файл, названный только одной из них, всё равно проверяется.

Два исключения, оба намеренных и оба покрытых тестами, потому что "слияние файл за файлом" — не вся история, и разница здесь — контроль цепочки поставок:

  • Плоский LAYA_SHA256_DIGESTS -- {artifact: digest}, а не {model: {...}} -- здесь вообще не слой. verify_digests применяет его сам, но только когда ничто другое не привязывает (if expected is None), так что ЛЮБОЙ expected_sha256, достигающий Agent, отсюда или из записи чекпойнта, означает, что плоская переменная не учитывается для этой загрузки. Проверено на реальных файлах: плоская переменная, привязывающая model.safetensors, плюс карта в agent_kwargs, привязывающая tokenizer.json, загружает подделанный model.safetensors. Используйте по-чекпойнтную форму или назовите каждый нужный файл в одной карте, если нужны оба. Это не изменилось относительно main.
  • Явная запись {} или None МАСКИРУЕТ то, что иначе применилось бы -- это и должно означать "загрузить это без проверки", и test_an_explicit_none_entry_masks_a_flat_environment_map закрепляет это.

А приоритет — по чекпойнту над общим, независимо от того, откуда взялся каждый, так что запись по чекпойнту, синтезированная из LAYA_SHA256_DIGESTS, побеждает expected_sha256, переданный здесь в коде. О том, что переменная окружения побеждает явный аргумент, стоит сказать прямо в таком контроле; test_an_environment_pin_overrides_the_shared_one_per_checkpoint — где это закреплено.

Для файла, названного обоими, побеждает запись по чекпойнту. Они не одинаково конкретны: карта agent_kwargs достигает каждого чекпойнта, который строит Router, а model.safetensors — единственное имя, которое каждый чекпойнт использует для разного файла, так что общая запись для него не может быть верным утверждением обо всех сразу. Ничто не возбуждает из-за этого пересечения -- отказ отверг бы общую привязку плюс переопределение по чекпойнту, а это обычная форма, которая загружалась корректно до всего этого. Если нужно знать, против какого дайджеста был проверен чекпойнт, прочитайте его обратно: карта, переданная каждому Agent, — это слияние, описанное выше.

И agent_kwargs, и sha256_digests публичны и изменяемы, а запись чекпойнта читается при загрузке, а не при конструировании, поэтому привязка, назначенная позже -- или добавленная к существующей записи на месте -- считается.

Имена, которые Router задаёт сам, -- model_id_or_path, device, token, subfolder, revision и аргументы хуков -- здесь отклоняются, а не молча затеняются, а оставшиеся имена проверяются по Agent.__init__ при конструировании, так что опечатка в опции падает на строке Router(...), а не на первом запросе.

Дайджесты артефактов — opt-in и всегда по модели: sha256_digests={"english": {...}} передаёт карту {path relative to the checkpoint dir: hexdigest} тому Agent, который её загружает, так что подделанный или подменённый файл весов отклоняется до его разбора. Нет Router-глобального эквивалента revision, потому что дайджесты, в отличие от commit SHA, не разделяемы: объединённый репозиторий поставляет отдельный model.safetensors для каждого из english, multilingual и typed-decisions, так что одна плоская карта может совпасть только с одним из них. Модель, указанная с None или {}, не добавляет собственных файлов, что загружает его без проверки, если только agent_kwargs["expected_sha256"] не привяжет его; он всё равно маскирует плоский LAYA_SHA256_DIGESTS, для чего такое указание и служит.

То же разделение доступно процессу, настроенному только через окружение: когда LAYA_SHA256_DIGESTS содержит карту с ключами по модели ({"english": {...}, "multilingual": {...}}), это задаёт её по чекпойнту, так что сервер, держащий несколько резидентных, может привязать каждый к своим собственным дайджестам вместо отказа запускаться на втором. Плоский LAYA_SHA256_DIGESTS сохраняет своё прежнее значение, применяемое laya.revisions к каждому чекпойнту, который процесс загружает, что верно для процесса с одним чекпойнтом. Запись аргумента имеет приоритет над окружением для модели, которую она называет. Вложенная переменная, называющая одни чекпойнты и не называющая другие, ничего не говорит об остальных: они сохраняют то, чем их привязывает agent_kwargs["expected_sha256"], потому что привязка одного чекпойнта из окружения — не просьба перестать проверять остальные.

Хуки — opt-in и выполняются на уровне Router: on_route видит решение маршрутизации, on_load / on_evict видят жизненный цикл модели, а on_predict_start / on_predict_end оборачивают весь вызов route+infer. См. laya.hooks.

Параметры

modelsOptional[Dict[str, str]]= None
deviceOptional[str]= None
tokenOptional[str]= None
revisionOptional[str]= None
revisionsOptional[Dict[str, Optional[str]]]= None
max_loadedint= 2
defaultstr= "english"
auto_task_detectionbool= False
standalone_reposbool= False
preloadbool= False
lang_guessOptional[Any]= None
hooks= None
on_predict_start= None
on_predict_end= None
hooks_raisebool= True
hooks_concurrentbool= True
hooks_timeoutOptional[float]= None
agent_kwargsOptional[Dict[str, Any]]= None
sha256_digestsOptional[Dict[str, Optional[Dict[str, str]]]]= None

load

load(name: str)

Возвращает Agent для name, скачивая и строя его при первом использовании.

Параллельные вызывающие разделяют один Agent вместо построения дубликатов.

Параметры

namestr

attach

attach(name: str, agent: Any)

Регистрирует уже построенный Agent под name вместо загрузки второй копии.

Полезно, когда у процесса чекпойнт загружен по другим причинам: демо, которое уже построило convaiinnovations/laya, может передать его роутеру, а не платить за -- и держать в памяти -- дубликат на 421M параметров.

Параметры

namestr
agentAny

preload

preload(names: Optional[List[str]] = None)

Скачивает и строит чекпойнты заранее, чтобы ни один запрос не платил за загрузку модели.

Холодная загрузка стоит секунды; определение языка стоит микросекунды. Когда каждый чекпойнт резидентен, маршрутизация фактически бесплатна -- именно это нужно в сервере или демо. max_loaded повышается, чтобы вместить и запрошенные чекпойнты, и все уже резидентные агенты, так что инкрементальная предзагрузка не вытесняет ни те, ни другие.

Параметры

namesOptional[List[str]]= None

unload

unload(name: Optional[str] = None)

Освобождает одну модель или все из них.

Параметры

nameOptional[str]= None

loaded_revisions

loaded_revisions: Dict[str, Optional[str]]

Commit SHA, из которого был загружен каждый резидентный агент (None для локальных путей).

route

route(
    state: Union[str, dict, list, None],
    questions: Optional[Dict[str, Any]] = None,
    model: Optional[str] = None,
    task: Optional[str] = None,
    lang: Optional[str] = None,
    lang_guess: Optional[Any] = None,
    hooks=None,
    hooks_raise: Optional[bool] = None,
    hooks_timeout: Optional[float] = None,
) -> RouteDecision

Решает, какой чекпойнт использовать, затем позволяет хукам on_route наблюдать за ним или заменить его.

ctx.decision — это RouteDecision; хук может заменить его (например, чтобы привязать чекпойнт), и замена — это то, что возвращается и используется. hooks — хуки на вызов, добавляемые после установленных на Router.

Параметры

stateUnion[str, dict, list, None]
questionsOptional[Dict[str, Any]]= None
modelOptional[str]= None
taskOptional[str]= None
langOptional[str]= None
lang_guessOptional[Any]= None
hooks= None
hooks_raiseOptional[bool]= None
hooks_timeoutOptional[float]= None

predict

predict(
    state: Union[str, dict, list],
    questions: Dict[str, Any],
    model: Optional[str] = None,
    task: Optional[str] = None,
    lang: Optional[str] = None,
    lang_guess: Optional[Any] = None,
    hooks=None,
    on_predict_start=None,
    on_predict_end=None,
    hooks_raise: Optional[bool] = None,
    hooks_timeout: Optional[float] = None,
    max_len: Optional[int] = None,
    head_max_len: Optional[int] = None,
    min_confidence: Optional[float] = None,
) -> Dict[str, Any]

Маршрутизирует, затем отвечает на каждый вопрос за один проход вперёд на выбранном чекпойнте.

Результат — обычная полезная нагрузка system_one плюс ключ routing, записывающий решение. Хуки on_predict_start / on_predict_end уровня Router оборачивают весь вызов route+infer и видят ctx.decision; см. laya.hooks. max_len / head_max_len переопределяют бюджет токенов агента для этого вызова (хук начала может задать ctx.max_len / ctx.head_max_len).

Параметры

stateUnion[str, dict, list]
questionsDict[str, Any]
modelOptional[str]= None
taskOptional[str]= None
langOptional[str]= None
lang_guessOptional[Any]= None
hooks= None
on_predict_start= None
on_predict_end= None
hooks_raiseOptional[bool]= None
hooks_timeoutOptional[float]= None
max_lenOptional[int]= None
head_max_lenOptional[int]= None
min_confidenceOptional[float]= None

predict_long

predict_long(
    state: Union[str, dict, list],
    questions: Dict[str, Any],
    model: Optional[str] = None,
    task: Optional[str] = None,
    lang: Optional[str] = None,
    lang_guess: Optional[Any] = None,
    window: Optional[int] = None,
    stride: Optional[int] = None,
    aggregate: str = "auto",
    batch_size: Optional[int] = None,
    hooks=None,
    on_predict_start=None,
    on_predict_end=None,
    hooks_raise: Optional[bool] = None,
    hooks_timeout: Optional[float] = None,
) -> Dict[str, Any]

Маршрутизирует, затем сканирует каждое окно состояния, а не только первое.

predict оценивает состояние из одного окна: всё, что дальше max_len, обрезается (первое окно, а для списка разговора — последнее) и никогда не доходит до модели. Этот метод маршрутизирует точно так же, как predict -- те же подсказки model/task/lang, те же хуки уровня роутера, тот же ключ routing и usage -- и оценивает маршрутизированное состояние с помощью predict_long того агента, который разбивает его на перекрывающиеся окна и агрегирует по вопросу. Правила агрегации — из laya.agent.Agent.predict_long: noul берёт сильнейшее окно, choice/score — наиболее уверенное.

Хуки на вызов (hooks, on_predict_start, on_predict_end, hooks_raise, hooks_timeout) оборачивают весь route+scan точно так же, как оборачивают predict: сканирование выполняется последним, так что побеждает хук начала, который отвечает (ctx.skip(...)) или переписывает состояние. max_len / head_max_len здесь не принимаются -- окно задаётся размером window или бюджетом чекпойнта, и переопределение усечения до одного окна — именно то, для чего нужен predict_long.

Параметры

stateUnion[str, dict, list]
questionsDict[str, Any]
modelOptional[str]= None
taskOptional[str]= None
langOptional[str]= None
lang_guessOptional[Any]= None
windowOptional[int]= None

токенов состояния на окно. По умолчанию — бюджет маршрутизированного чекпойнта (max_len - head_max_len - 8), ограниченный местом, которое вопросы оставляют для состояния, так что ни одно окно не усекается снова на входе; меньшее окно лучше изолирует локализованный фрагмент.

strideOptional[int]= None

шаг токенов между окнами; по умолчанию — половина эффективного окна (перекрытие 50%). Шаг больше этого окна — ValueError, так как токены между окнами не дошли бы ни до какой модели.

aggregatestr= "auto"

«auto» (правила по типу выше) — единственный режим.

batch_sizeOptional[int]= None

предел окон на проход вперёд, чтобы ограничить память на очень длинных состояниях.

hooks= None
on_predict_start= None
on_predict_end= None
hooks_raiseOptional[bool]= None
hooks_timeoutOptional[float]= None

Возвращает

Обычная полезная нагрузка predict, где usage["windows"] считает оценённые окна.

Исключения

TypeError: у маршрутизированного агента нет predict_long -- присоединённого вручную, поскольку и Agent, и ONNXAgent его реализуют -- так что сканировать нечем. Возбуждается всегда, каким бы ни был hooks_raise, как и любая другая ошибка, которую возбуждает само сканирование -- ни одна не перехватывается: сканирование — это работа самого метода, а не хук вызывающего, поэтому политика ошибок хуков не решает, можно ли его пропустить. Раньше он выполнялся как хук начала, где hooks_raise=False проглатывал это и возвращал одно окно, оценённое system_one -- вопрос, отличный от заданного. Агент, у predict_long которого нет параметра lang, сканируется без него и получает предупреждение, а не отказ; это проверка сигнатуры, а не проглоченная ошибка.

decide

decide(
    state: Union[str, dict, list],
    schema: Any = None,
    questions: Optional[Dict[str, Any]] = None,
    return_details: bool = False,
    min_confidence: Optional[float] = None,
    predict_kwargs,
) -> Any

Отвечает на state относительно схемы (JSON-схемы или модели pydantic) и возвращает типизированные значения.

См. laya.structured. Передайте ровно одно из schema или questions; дополнительные именованные аргументы (например, model=, task=, hooks=) передаются в predict.

Параметры

stateUnion[str, dict, list]
schemaAny= None
questionsOptional[Dict[str, Any]]= None
return_detailsbool= False
min_confidenceOptional[float]= None
predict_kwargs

decide_batch

decide_batch(
    states: Sequence[Any],
    schema: Any = None,
    questions: Optional[Dict[str, Any]] = None,
    return_details: bool = False,
    min_confidence: Optional[float] = None,
    predict_kwargs,
) -> List[Any]

Отвечает на многие состояния относительно одной схемы (JSON-схемы или модели pydantic) за один пакетный вызов.

Пропускная форма :meth:decide: схема планируется один раз, и её вопросы выполняются по каждому состоянию через :meth:predict_batch (сгруппированные проходы вперёд, результаты в порядке входа), затем ответы каждого состояния проецируются так же, как это делает decide. Дополнительные именованные аргументы (batch_size=, model=, hooks=, ...) передаются в predict_batch. См. laya.structured.

Параметры

statesSequence[Any]
schemaAny= None
questionsOptional[Dict[str, Any]]= None
return_detailsbool= False
min_confidenceOptional[float]= None
predict_kwargs

route_batch

route_batch(
    requests: Sequence[Dict[str, Any]],
    hooks_timeout: Optional[float] = None,
    hooks=None,
    hooks_raise: Optional[bool] = None,
) -> List[RouteDecision]

Маршрутизирует неоднородный пакет запросов, не загружая ни одного чекпойнта.

Каждый запрос — это отображение с state и questions плюс те же необязательные переопределения маршрутизации, которые принимает :meth:route: model, task, lang и lang_guess. Возвращаемые решения сохраняют порядок входа.

Это намеренно отделено от инференса, чтобы вызывающие могли изучить или агрегировать решения маршрутизации, прежде чем платить за загрузку модели.

Параметры

requestsSequence[Dict[str, Any]]

Последовательность словарей запросов, каждый требует state и questions.

hooks_timeoutOptional[float]= None

Переопределяет hooks_timeout Router для этого вызова, применяемый к диспетчеризации on_route каждого запроса, как в :meth:route.

hooksHookArg= None

Хук на вызов или последовательность хуков для этого вызова.

hooks_raiseOptional[bool]= None

Переопределяет политику hooks_raise Router для этого вызова.

predict_batch

predict_batch(
    requests: Sequence[Dict[str, Any]],
    batch_size: Optional[int] = None,
    hooks_timeout: Optional[float] = None,
    min_confidence: Optional[float] = None,
    sort_by_length: bool = False,
    hooks=None,
    on_predict_start=None,
    on_predict_end=None,
    hooks_raise: Optional[bool] = None,
) -> List[Dict[str, Any]]

Маршрутизирует и выполняет неоднородный пакет запросов с минимальным движением моделей.

Запросы сначала маршрутизируются и группируются по чекпойнту. Внутри каждого чекпойнта запросы, разделяющие одну схему вопросов, передаются в Agent.predict_batch, чтобы их состояния могли разделять проходы вперёд. Затем результаты восстанавливаются в исходный порядок запросов.

Запросы могут независимо задавать model, task, lang, lang_guess, max_len или head_max_len и использовать разные схемы вопросов. max_len / head_max_len — это форма переопределения бюджета токенов по запросу, которую predict принимает как аргументы вызова: они задают бюджеты состояния и головы вопросов чекпойнта для этого одного запроса, так что широкий вопрос можно задать, не сужая остальные запросы пакета до того же окна. Запросы, требующие разных бюджетов, разбиваются на отдельные проходы вперёд, поскольку один вызов Agent.predict_batch несёт один бюджет для всех своих состояний. Хук начала всё ещё может заменить любое из значений на ctx.

Хуки предсказания уровня Router выполняются по запросу, как их выполняет predict: каждый запрос получает свой PredictContext, так что on_predict_start может заменить состояние, вопросы или бюджет токенов этого запроса, или сделать ему ctx.skip(...), а on_predict_end видит и может заменить его результат. Запросы группируются для прохода вперёд после того, как выполнились их хуки начала, и запросы группы чекпойнта завершаются в обратном порядке относительно того, в котором начались. Если группа чекпойнта падает, каждый её запрос, чей хук начала выполнился, падает с исключением, включая попадание в кэш: каждый получает on_error, а затем on_predict_end до того, как исключение распространится.

Параметры

requestsSequence[Dict[str, Any]]

Последовательность словарей запросов. Каждый элемент требует state и questions и может включать переопределения маршрутизации model, task, lang или lang_guess и переопределения бюджета токенов max_len / head_max_len.

batch_sizeOptional[int]= None

Необязательное максимальное число состояний на пакет прохода вперёд Agent.

hooks_timeoutOptional[float]= None

Переопределяет hooks_timeout Router для этого вызова.

min_confidenceOptional[float]= None

Необязательный float или сопоставление по бакетам для gating по уверенности.

sort_by_lengthbool= False

Передаётся в каждый вызов Agent.predict_batch, так что каждая группа вопросов дополняется до более короткого максимума; см. Agent.predict_batch. Результаты сохраняют порядок входа в любом случае. Молча отбрасывается для присоединённого агента, чей predict_batch старше этой опции (#294).

hooksHookArg= None

Хук на вызов или последовательность хуков для этого вызова.

on_predict_startPredictHookArg= None

Обычный callable или последовательность callables для событий начала.

on_predict_endPredictHookArg= None

Обычный callable или последовательность callables для событий конца.

hooks_raiseOptional[bool]= None

Переопределяет политику hooks_raise Router для этого вызова.

Возвращает

Один обычный результат предсказания Router на запрос, в том же порядке, что и вход.

RouteDecision

RouteDecision()

Базовые классы: dict

Результат маршрутизации: какая модель, почему и что было обнаружено.

Ведёт себя как словарь, так что сериализуется прямо в ответ API.

DEFAULT_MODELS

DEFAULT_MODELS = {
    "english": (BUNDLE_REPO, None),
    "multilingual": (BUNDLE_REPO, "multilingual"),
    "typed-decisions": (BUNDLE_REPO, "typed-decisions"),
}