Questions et réponses
Une question Laya est une décision typée, et le type décide à la fois de ce que tu demandes et de ce que tu récupères. Il y en a trois, et un seul état peut les porter toutes en une seule passe avant :
import laya
agent = laya.load("convaiinnovations/laya")
questions = {
"dept": {"type": "choice", "instructions": "Which team should handle this?",
"criteria": {"billing": "money and invoices",
"technical": "bugs and outages",
"sales": "pricing and contracts"}},
"urgent": {"type": "noul", "instructions": "Is this urgent?"},
"severity": {"type": "score", "instructions": "How severe is this?",
"criteria": ["trivial", "minor", "moderate", "serious", "critical"]},
}
result = agent.system_one({"text": "I was charged twice and nobody has replied for a week. "
"Please refund me."}, questions)
result["answers"]["dept"]["choice"] # 'billing'
result["answers"]["urgent"]["noul"] # 0.8727
result["answers"]["severity"]["score"] # 2.9046
Une seule passe avant répond aux trois. C’est tout l’intérêt de l’interface typée : une question noul
n’est pas un choice à deux options qui se trouvent être « oui » et « non » — c’est une tête différente
avec une forme de sortie différente, et le type dit à Laya laquelle utiliser.
Les trois types
choice — choisir un élément d’un ensemble
{"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {"billing": "money and invoices", "technical": "bugs and outages"}}
criteria est un mapping ordonné de libellé vers description. L’ordre est positionnel dans la
question rendue, donc deux questions avec les mêmes libellés dans un ordre différent sont des questions
différentes. Une description est optionnelle ; {"billing": None} rend le libellé tout seul. Les libellés
sont renvoyés exactement comme tu les as écrits, donc un libellé non-chaîne revient tel quel dans choice
et comme clé dans probabilities.
La description vaut la peine d’être écrite. Ce n’est pas de la décoration : le texte de question rendu est
ce que lit le modèle, donc un {"a": None, "b": None} nu ne lui donne rien pour distinguer les options.
{"type": "choice", "choice": "billing",
"probabilities": {"billing": 0.9881, "technical": 0.0057, "sales": 0.0062},
"confidence": 0.9339, "answer_confidence": 0.9881,
"action": {"act_probability": 1.0}}
noul — oui ou non
{"type": "noul", "instructions": "Is this urgent?"}
noul est le nom du projet pour une décision à
deux voies, et sa réponse est la probabilité de vrai, pas un booléen seuillé :
{"type": "noul", "noul": 0.8727, "confidence": 0.8727, "answer_confidence": 0.8727,
"action": {"act_probability": 1.0}}
Le seuil est à toi de choisir, parce qu’il dépend de ce que te coûte un faux positif. Il n’y a pas de
champ bool à confondre avec une décision.
Tu peux relibeller les deux options quand la décision n’est pas naturellement un oui/non — labels prend
exactement les clés false et true, et la polarité est inchangée : noul est toujours P(true).
{"type": "noul", "instructions": "Does this need a human?",
"labels": {"false": "automatic", "true": "escalate"}}
Les libellés ne sont pas un moyen de corriger une question que le modèle se trompe. noul suit ses
propres libellés d’option, le plus fortement sur le checkpoint anglais, donc une paire de libellés qui se
lit comme une décision (« approve » / « reject ») peut tirer la réponse vers le libellé plutôt que vers
l’état. Valide tout relibellage sur tes propres données avant de t’y fier.
score — un niveau ordonné
{"type": "score", "instructions": "How severe is this?",
"criteria": ["trivial", "minor", "moderate", "serious", "critical"]}
criteria est une liste ordonnée, et elle doit être ordonnée par ordre croissant — la position est
l’échelle.
{"type": "score", "score": 2.9046,
"legend": {"0": "trivial", "1": "minor", "2": "moderate", "3": "serious", "4": "critical"},
"probabilities": {"0": 0.0134, "1": 0.05, "2": 0.0518, "3": 0.7881, "4": 0.0967},
"confidence": 0.5187, "answer_confidence": 0.7881,
"action": {"act_probability": 1.0}}
score est une valeur attendue, pas le niveau le plus probable. Ci-dessus, score vaut 2.90 alors
que le niveau le plus probable unique est serious (3) à 0.788. Les deux sont utiles et répondent à des
questions différentes : la valeur attendue minimise l’erreur quadratique sur l’échelle, l’argmax minimise
le désaccord avec le modèle. Si tu veux le libellé, prends l’argmax de probabilities ou lis la
contrepartie de answer_confidence dans la légende — n’arrondis pas score en supposant que c’est le
libellé. legend existe pour que tu n’aies jamais à deviner quel index signifie quoi.
Lire la confiance
Chaque réponse porte deux nombres de confiance, et ils mesurent des choses différentes.
| champ | ce que c’est | gater dessus ? |
|---|---|---|
answer_confidence |
max(p) — la probabilité de la réponse rapportée |
oui, après ajustement |
confidence |
1 - H(p) / log(k) — à quel point toute la distribution est concentrée |
non |
probabilities |
la distribution complète (choice, score) |
— |
answer_confidence est la quantité que l’échelonnage de température ajuste et sur laquelle chaque chiffre
de calibration du dépôt est calculé, ce qui en fait celle sur laquelle gater. Elle n’est pas calibrée
telle quelle : la propriété qu’on lui attribue d’habitude — que sur les réponses renvoyées à la
confiance c, environ c d’entre elles sont correctes — ne tient qu’une fois les températures ajustées et
validées sur des données mises de côté pour ton checkpoint à ton nombre d’options. Les checkpoints
livrés sont trop confiants et l’écart dépend du nombre d’options, donc un seuil non ajusté peut sélectionner
en dessous de l’exactitude du modèle lui-même (#394).
# THRESHOLD is a number you measured on your own held-out data, not one the model ships.
# Fit and validate the temperatures first — the fine-tuning notebook has the loop:
# notebooks/laya_finetune_typed_decisions_2xT4_kaggle.ipynb
ans = result["answers"]["dept"]
if ans["answer_confidence"] >= THRESHOLD:
...
confidence est l’entropie normalisée : élevée quand la distribution est pointue, basse quand elle est
étalée, que la réponse du haut soit correcte ou non. C’est un signal utile et il n’est pas sur la même
échelle, donc les deux ne doivent pas être gatés contre un seul nombre :
# the same three answers, and the two numbers are not the same
dept confidence 0.9339 answer_confidence 0.9881
urgent confidence 0.8727 answer_confidence 0.8727
severity confidence 0.5187 answer_confidence 0.7881
Pour noul, ils sont égaux par construction — sur deux options, max(p, 1-p) vaut max(p) — donc une
réponse noul ne peut pas te dire lequel des deux tu as lu. Les deux clés sont présentes sur chaque type,
pour que le choix soit explicite plutôt qu’implicite.
Un seuil est une politique, pas une propriété du modèle. Les deux checkpoints sont livrés trop
confiants, et à quel point dépend du nombre d’options, donc un nombre mesuré sur une question à 3 options
ne se transfère pas à une question à 20 options. Mesure-le sur tes propres données ; la section Calibration
de BENCHMARKS.md a la boucle d’ajustement
et les valeurs ajustées.
action et act_probability
action.act_probability est le score d’une tête séparée pour « un agent devrait-il agir là-dessus du
tout », distinct de la confiance de la réponse elle-même. Il est rapporté pour chaque type de question.
Rien dans la bibliothèque ne le met en seuil pour toi.
Presets
Trois ensembles de questions tout faits, pour que les cas courants n’aient pas besoin de critères écrits à la main :
from laya import triage_questions, guard_questions, moderation_questions
agent.system_one(ticket, triage_questions())
Utilise-les comme point de départ plutôt que comme contrat — lis les questions qu’ils produisent avec
render_options et vérifie que les libellés conviennent à ton domaine avant de les livrer.
Relire les options
Comme l’ordre des options est positionnel et que le texte des options est ce que lit le modèle, il vaut la peine de pouvoir voir exactement ce qui a été envoyé :
from laya import render_options
render_options({"t": "choice", "crit": {"billing": None, "sales": "pricing"}})
# ['billing', 'sales: pricing']
render_options({"t": "score", "crit": ["low", "high"]})
# ['level 0: low', 'level 1: high']
Les mêmes libellés dans un ordre différent se rendent dans cet ordre, c’est pourquoi l’ordre fait partie de l’identité de la question :
render_options({"t": "choice", "crit": {"x": "first", "y": "second"}})
# ['x: first', 'y: second']
render_options({"t": "choice", "crit": {"y": "second", "x": "first"}})
# ['y: second', 'x: first']
Note les clés. render_options prend la forme interne à clés courtes {"t": ..., "crit": ...}, pas
la forme {"type": ..., "criteria": ...} que tu écris dans une question — passer la forme publique lève
KeyError: 't'.
La conversion est un mapping court et stable que tu peux inliner, ce qui évite d’aller chercher dans une
aide privée — Agent._to_internal est interne et peut changer :
def as_internal(q):
"""The short-key shape `render_options` reads, from a question as you wrote it."""
crit = q.get("criteria")
if q["type"] == "choice" and isinstance(crit, list):
crit = {c: None for c in crit}
return {"t": q["type"], "ins": q["instructions"], "crit": crit}
render_options(as_internal(question))
Cela reflète ce que fait la bibliothèque pour une question choice écrite comme liste de libellés ; un dict
criteria et une liste score passent tels quels.
Limites à connaître avant de concevoir autour de ça
- La confiance n’est pas une garantie de justesse à nombre d’options élevé. Sur une question à 20 options, les distributions des réponses justes et fausses se recouvrent fortement, et un seuil peut finir par sélectionner en dessous de l’exactitude du modèle lui-même. Voir #394.
- La négation n’est pas gérée de façon fiable dans les questions à choix forcé. Une question d’annulation peut renvoyer le libellé d’annulation pour un état qui dit de ne pas annuler, avec une confiance élevée, sur les deux checkpoints. Voir #377.
noulpeut suivre ses libellés plutôt que l’état, donc valide tout relibellage.- Plus d’options n’est pas gratuit. Au-delà d’environ 20, le modèle se dégrade vite ; utilise l’aide de présélection pour réduire un grand espace de libellés avant de demander, ou découpe-le en une question grossière et une fine.