Documentation

Router

laya.Router détecte la langue de chaque état et envoie la requête au checkpoint correspondant, en chargeant les checkpoints au premier usage.

Les noms, types, valeurs par défaut et le code restent en anglais ; le reste est traduit (les entrées non encore traduites s'affichent dans l'original anglais).

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,
)

Classes de base: HookRegistry

Charge paresseusement les checkpoints Laya et envoie chaque requête au bon.

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

Les modèles sont téléchargés et construits au premier usage. max_loaded plafonne combien restent résidents (le moins récemment utilisé est évincé), car les trois ensemble font ~1,16B paramètres.

Le défaut est 2, car le routage automatique ne choisit jamais qu’entre english et multilingual : un plafond de un reconstruirait le checkpoint qu’il vient d’évincer à chaque changement de script, soit des secondes par requête sur exactement le trafic pour lequel le Router existe. Le trafic qui ne voit jamais qu’une seule langue ne construit jamais le second checkpoint, donc le défaut ne lui coûte rien. Baisse-le à 1 pour un hôte à mémoire contrainte, et monte-le à 3 (ou précharge) quand auto_task_detection, un model= explicite ou un task= explicite peuvent aussi atteindre typed-decisions.

Pour un serveur ou une démo, précharge plutôt : un chargement à froid coûte des secondes, alors que la détection coûte des microsecondes, donc même le défaut paie encore un chargement la première fois qu’une langue apparaît.

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

Les révisions Hub sont optionnelles. revision applique un commit à chaque modèle ; revisions={"english": "...", "multilingual": "..."} le remplace par modèle, ce qui est utile quand des dépôts autonomes ont été revus à des commits différents. Sans ni l’un ni l’autre, le défaut normal de huggingface_hub et le cache hors ligne existant sont utilisés.

Une entrée revisions qui vaut None ou est vide signifie « aucune substitution pour ce modèle », donc le modèle hérite de revision -- la forme qu’écrit {"english": os.environ.get("EN_SHA")} quand la variable n’est pas définie, ce qui ne doit pas coûter à l’appelant l’épinglage qu’il a bien demandé. Rien dans revisions ne peut détacher un modèle pendant que revision épingle les autres ; laisse revision non défini et nomme plutôt les modèles que tu veux épingler.

Une revision vide est lue de la même façon, ce qui est un changement délibéré de ce que le Router transmet : Router(revision=" ") atteignait auparavant resolve_revision, où une chaîne vraie mais vide supprimait le repli LAYA_REVISION et laissait le défaut de huggingface_hub, et est maintenant écartée avant d’y arriver, donc un Router configuré avec des espaces se comporte comme un configuré sans rien et $LAYA_REVISION s’applique. C’est ce que « cette ligne de configuration n’a jamais été remplie » doit vouloir dire si revision et une entrée revisions doivent être lus de la même façon.

C’est l’un des deux endroits où une source plus faible passe devant un argument explicite. L’autre est une entrée de condensat par checkpoint issue de LAYA_SHA256_DIGESTS, qui l’emporte sur un expected_sha256 transmis via agent_kwargs ; voir la docstring de la classe.

Tout autre chose qu’accepte laya.Agent est accessible via agent_kwargs, qui est fusionné dans chaque checkpoint que le Router construit :

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 y épingle les mêmes fichiers sur chaque checkpoint, ce que veut un seul checkpoint résident ou un tokenizer.json partagé. Il n’est jamais écarté en bloc par les condensats propres d’un checkpoint : là où l’un en a aussi une entrée, de sha256_digests ou d’un LAYA_SHA256_DIGESTS par checkpoint, les deux tables sont fusionnées fichier par fichier, donc un fichier qu’une seule des deux nomme est quand même vérifié.

Deux exceptions, toutes deux délibérées et toutes deux testées, car « fusionné fichier par fichier » n’est pas toute l’histoire et la différence est un contrôle de chaîne d’approvisionnement :

  • Un LAYA_SHA256_DIGESTS plat -- {artifact: digest} plutôt que {model: {...}} -- n’est pas une couche ici du tout. verify_digests l’applique lui-même, mais seulement quand rien d’autre n’épingle (if expected is None), donc N’IMPORTE QUEL expected_sha256 atteignant Agent, d’ici ou d’une entrée de checkpoint, signifie que la variable plate n’est pas consultée pour ce chargement. Vérifié sur de vrais fichiers : une variable plate épinglant model.safetensors plus une table agent_kwargs épinglant tokenizer.json charge un model.safetensors altéré. Utilise la forme par checkpoint, ou nomme chaque fichier qui t’importe dans une seule table, si tu as besoin des deux. Ceci est inchangé par rapport à main.
  • Une entrée explicite {} ou None MASQUE ce qui s’appliquerait autrement -- c’est ce que « charger celui-ci sans vérification » doit vouloir dire, et test_an_explicit_none_entry_masks_a_flat_environment_map l’épingle.

Et la priorité est par checkpoint sur partagé, peu importe d’où vient chacun, donc une entrée par checkpoint synthétisée à partir de LAYA_SHA256_DIGESTS l’emporte sur un expected_sha256 passé ici dans le code. Une variable d’environnement qui bat un argument explicite mérite d’être dite clairement sur un contrôle comme celui-ci ; test_an_environment_pin_overrides_the_shared_one_per_checkpoint est l’endroit où cela est épinglé.

Pour un fichier que les deux nomment, l’entrée par checkpoint l’emporte. Les deux ne sont pas également spécifiques : la table agent_kwargs atteint chaque checkpoint que le Router construit, et model.safetensors est le seul nom que chaque checkpoint utilise pour un fichier différent, donc une entrée partagée pour lui ne peut pas être une revendication correcte à propos de tous à la fois. Rien ne lève sur ce chevauchement -- le refuser rejetterait un épinglage partagé plus une substitution par checkpoint, ce qui est la forme ordinaire et qui se chargeait correctement avant que tout cela existe. Si tu as besoin de savoir contre quel condensat un checkpoint a été vérifié, relis-le : la table remise à chaque Agent est la fusion décrite ci-dessus.

Tant agent_kwargs que sha256_digests sont publics et mutables, et l’entrée d’un checkpoint est lue au chargement plutôt qu’à la construction, donc un épinglage assigné ensuite -- ou ajouté à une entrée existante sur place -- compte.

Les noms que le Router se fixe à lui-même -- model_id_or_path, device, token, subfolder, revision et les arguments de hook -- sont refusés ici plutôt que masqués en silence, et les noms restants sont vérifiés contre Agent.__init__ à la construction, donc une option mal orthographiée échoue sur la ligne Router(...) au lieu de la première requête.

Les empreintes d’artefacts sont optionnelles et toujours par modèle : sha256_digests={"english": {...}} transmet cette table {path relative to the checkpoint dir: hexdigest} à l’Agent qui le charge, donc un fichier de poids altéré ou substitué est refusé avant d’être analysé. Il n’existe pas d’équivalent de revision à l’échelle du Router, car les empreintes, contrairement à un commit SHA, ne sont pas partageables : le dépôt groupé livre un model.safetensors distinct pour chacun de english, multilingual et typed-decisions, donc une table plate ne peut jamais correspondre qu’à un seul d’entre eux. Un modèle listé avec None ou {} n’ajoute aucun fichier propre, ce qui le charge sans vérification sauf si agent_kwargs["expected_sha256"] l’épingle ; il masque encore un LAYA_SHA256_DIGESTS plat, ce à quoi sert de le lister ainsi.

La même séparation est disponible pour un processus configuré uniquement par l’environnement : quand LAYA_SHA256_DIGESTS contient une table indexée par modèle ({"english": {...}, "multilingual": {...}}) celle-ci l’amorce par checkpoint, donc un serveur qui en garde plusieurs résidents peut épingler chacun avec ses propres empreintes au lieu de refuser de démarrer sur le second. Un LAYA_SHA256_DIGESTS plat conserve sa signification existante, appliqué par laya.revisions à chaque checkpoint que le processus charge, ce qui convient à un processus à checkpoint unique. Une entrée d’argument l’emporte sur l’environnement pour le modèle qu’elle nomme. Une variable imbriquée qui nomme certains checkpoints et pas d’autres ne dit rien sur les autres : ils gardent ce avec quoi agent_kwargs["expected_sha256"] les épingle, car épingler un checkpoint depuis l’environnement n’est pas une demande d’arrêter de vérifier le reste.

Les hooks sont optionnels et s’exécutent au niveau du Router : on_route voit la décision de routage, on_load / on_evict voient le cycle de vie du modèle, et on_predict_start / on_predict_end enveloppent tout l’appel route+inférence. Voir laya.hooks.

Paramètres

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)

Renvoie l’Agent pour name, en le téléchargeant et le construisant au premier usage.

Les appelants concurrents partagent un seul Agent au lieu de construire des doublons.

Paramètres

namestr

attach

attach(name: str, agent: Any)

Enregistre un Agent déjà construit sous name au lieu de charger une seconde copie.

Utile quand le processus a un checkpoint chargé pour d’autres raisons : une démo qui a déjà construit convaiinnovations/laya peut le confier au router plutôt que de payer -- et garder en mémoire -- un doublon de 421M paramètres.

Paramètres

namestr
agentAny

preload

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

Télécharge et construit les checkpoints à l’avance pour qu’aucune requête ne paie un chargement de modèle.

Un chargement à froid coûte des secondes ; la détection de langue coûte des microsecondes. Avec chaque checkpoint résident, le routage est effectivement gratuit -- ce que tu veux dans un serveur ou une démo. max_loaded est augmenté pour contenir à la fois les checkpoints demandés et tous les agents déjà résidents, donc le préchargement incrémental n’évince ni les uns ni les autres.

Paramètres

namesOptional[List[str]]= None

unload

unload(name: Optional[str] = None)

Libère un modèle, ou tous.

Paramètres

nameOptional[str]= None

loaded_revisions

loaded_revisions: Dict[str, Optional[str]]

Commit SHA depuis lequel chaque agent résident a été chargé (None pour les chemins locaux).

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

Décide quel checkpoint utiliser, puis laisse les hooks on_route l’observer ou le remplacer.

ctx.decision est la RouteDecision ; un hook peut la remplacer (par exemple pour épingler un checkpoint) et le remplacement est ce qui est renvoyé et utilisé. hooks sont des hooks par appel, ajoutés après ceux installés sur le Router.

Paramètres

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]

Route, puis répond à chaque question en une seule passe avant sur le checkpoint choisi.

Le résultat est la charge habituelle de system_one plus une clé routing qui enregistre la décision. Les hooks on_predict_start / on_predict_end au niveau du Router enveloppent tout l’appel route+inférence et voient ctx.decision ; voir laya.hooks. max_len / head_max_len remplacent le budget de jetons de l’agent pour cet appel (un hook de début peut fixer ctx.max_len / ctx.head_max_len).

Paramètres

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]

Route, puis parcourt chaque fenêtre de l’état au lieu de seulement la première.

predict score un état depuis une seule fenêtre : tout ce qui dépasse max_len est coupé (la première fenêtre, ou pour une liste de conversation la dernière) et n’atteint jamais le modèle. Ceci route exactement comme le fait predict -- les mêmes indications model/task/lang, les mêmes hooks au niveau du router, la même clé routing et usage -- et score l’état routé avec le predict_long de cet agent, qui le découpe en fenêtres chevauchantes et agrège par question. Les règles d’agrégation sont celles de laya.agent.Agent.predict_long : noul prend la fenêtre la plus forte, choice/score la plus confiante.

Les hooks par appel (hooks, on_predict_start, on_predict_end, hooks_raise, hooks_timeout) enveloppent tout le route+scan exactement comme ils enveloppent predict : le parcours s’exécute en dernier, donc un hook de début qui répond (ctx.skip(...)) ou réécrit l’état l’emporte. max_len / head_max_len ne sont pas acceptés ici -- une fenêtre est dimensionnée par window ou par le budget du checkpoint, et remplacer la troncature à fenêtre unique est précisément le rôle de predict_long.

Paramètres

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

jetons d’état par fenêtre. Par défaut le budget du checkpoint routé (max_len - head_max_len - 8), plafonné à la place que les questions laissent pour l’état, pour qu’aucune fenêtre ne soit de nouveau tronquée à l’entrée ; une fenêtre plus petite isole un intervalle localisé.

strideOptional[int]= None

pas de jetons entre les fenêtres ; par défaut la moitié de la fenêtre effective (50 % de chevauchement). Un pas au-delà de cette fenêtre est un ValueError, car les jetons entre les fenêtres n’atteindraient aucun modèle.

aggregatestr= "auto"

« auto » (les règles par type ci-dessus) est le seul mode.

batch_sizeOptional[int]= None

plafond du nombre de fenêtres par passe avant, pour borner la mémoire sur de très longs états.

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

Renvoie

La charge habituelle de predict, avec usage["windows"] qui compte les fenêtres scorées.

Exceptions

TypeError : l’agent routé n’a pas de predict_long -- un agent attaché à la main, puisque Agent comme ONNXAgent l’implémentent --, donc il n’y a rien avec quoi parcourir. Toujours levée, quel que soit hooks_raise, et de même tout autre erreur que le parcours lui-même lève -- aucune n’est attrapée : le parcours est le travail propre de cette méthode, pas un hook d’un appelant, donc la politique d’erreurs de hooks ne décide pas s’il peut être ignoré. Il tournait auparavant comme hook de début, où hooks_raise=False l’avalait et renvoyait une fenêtre scorée par system_one -- une autre question que celle posée. Un agent dont le predict_long n’a pas de paramètre lang est parcouru sans en avoir et averti, pas mis en échec ; c’est une vérification de signature, pas une erreur avalée.

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

Répond à state contre un schéma (schéma JSON ou modèle pydantic) et renvoie des valeurs typées.

Voir laya.structured. Passe exactement l’un de schema ou questions ; les arguments nommés supplémentaires (par exemple model=, task=, hooks=) sont transmis à predict.

Paramètres

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]

Répond à de nombreux états contre un seul schéma (schéma JSON ou modèle pydantic) en un appel par lots.

La forme haut débit de :meth:decide : le schéma est planifié une fois et ses questions tournent sur chaque état via :meth:predict_batch (passes avant groupées, résultats dans l’ordre d’entrée), puis les réponses de chaque état sont projetées comme le fait decide. Les arguments nommés supplémentaires (batch_size=, model=, hooks=, ...) sont transmis à predict_batch. Voir laya.structured.

Paramètres

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]

Route un lot de requêtes hétérogènes sans charger aucun checkpoint.

Chaque requête est un mapping avec state et questions plus les mêmes substitutions d’options de routage acceptées par :meth:route : model, task, lang et lang_guess. Les décisions renvoyées conservent l’ordre d’entrée.

Ceci est volontairement séparé de l’inférence pour que les appelants puissent inspecter ou agréger les décisions de routage avant de payer le coût de chargement du modèle.

Paramètres

requestsSequence[Dict[str, Any]]

Séquence de dictionnaires de requête, chacun exigeant state et questions.

hooks_timeoutOptional[float]= None

Remplace le hooks_timeout du Router pour cet appel, appliqué à la diffusion on_route de chaque requête, comme sur :meth:route.

hooksHookArg= None

Hook par appel ou séquence de hooks pour cet appel.

hooks_raiseOptional[bool]= None

Remplace la politique hooks_raise du Router pour cet appel.

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]]

Route et exécute un lot de requêtes hétérogènes avec un minimum de changements de modèle.

Les requêtes sont d’abord routées et groupées par checkpoint. Au sein de chaque checkpoint, les requêtes qui partagent le même schéma de questions sont transmises à Agent.predict_batch pour que leurs états puissent partager des passes avant. Les résultats sont ensuite rétablis dans l’ordre d’origine des requêtes.

Les requêtes peuvent spécifier indépendamment model, task, lang, lang_guess, max_len ou head_max_len et peuvent utiliser des schémas de questions différents. max_len / head_max_len sont la forme par requête de la substitution du budget de jetons que predict prend comme arguments d’appel : ils fixent les budgets d’état et de tête de questions du checkpoint pour cette seule requête, de sorte qu’une question large peut être posée sans réduire les autres requêtes du lot à la même fenêtre. Les requêtes qui demandent des budgets différents sont réparties en passes avant distinctes, puisqu’un appel Agent.predict_batch porte un seul budget pour tous ses états. Un hook de début peut encore remplacer l’une ou l’autre valeur sur ctx.

Les hooks de prédiction au niveau du Router s’exécutent par requête, comme predict les exécute : chaque requête obtient son propre PredictContext, donc on_predict_start peut remplacer l’état, les questions ou le budget de jetons de cette requête, ou lui faire ctx.skip(...), et on_predict_end voit et peut remplacer son résultat. Les requêtes sont groupées pour la passe avant après que leurs hooks de début ont tourné, et les requêtes d’un groupe de checkpoint se terminent dans l’ordre inverse de celui où elles ont commencé. Si un groupe de checkpoint échoue, chaque requête de ce groupe dont le hook de début a tourné échoue avec l’exception, y compris un succès de cache : chacune reçoit on_error puis on_predict_end avant que l’exception ne se propage.

Paramètres

requestsSequence[Dict[str, Any]]

Séquence de dictionnaires de requête. Chaque élément exige state et questions et peut inclure les substitutions de routage model, task, lang ou lang_guess et les substitutions de budget de jetons max_len / head_max_len.

batch_sizeOptional[int]= None

Nombre maximal optionnel d’états par lot de passes avant de l’Agent.

hooks_timeoutOptional[float]= None

Remplace le hooks_timeout du Router pour cet appel.

min_confidenceOptional[float]= None

Float optionnel ou mapping par bucket pour le gating par confiance.

sort_by_lengthbool= False

Transmis à chaque appel Agent.predict_batch, donc chaque groupe de questions complète jusqu’à un maximum plus court ; voir Agent.predict_batch. Les résultats conservent l’ordre d’entrée dans les deux cas. Silencieusement abandonné pour un agent attaché dont le predict_batch est antérieur à ce réglage (#294).

hooksHookArg= None

Hook par appel ou séquence de hooks pour cet appel.

on_predict_startPredictHookArg= None

Callable simple ou séquence de callables pour les événements de début.

on_predict_endPredictHookArg= None

Callable simple ou séquence de callables pour les événements de fin.

hooks_raiseOptional[bool]= None

Remplace la politique hooks_raise du Router pour cet appel.

Renvoie

Un résultat de prédiction normal du Router par requête, dans le même ordre que l’entrée.

RouteDecision

RouteDecision()

Classes de base: dict

Le résultat du routage : quel modèle, pourquoi et ce qui a été détecté.

Se comporte comme un dict, donc il se sérialise directement dans une réponse d’API.

DEFAULT_MODELS

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