Fragen und Antworten
Eine Laya-Frage ist eine typisierte Entscheidung, und der Typ entscheidet sowohl, was du fragst, als auch, was du zurückbekommst. Es gibt drei, und ein Zustand kann alle in einem einzigen Forward-Pass tragen:
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
Ein einziger Forward-Pass beantwortet alle drei. Das ist der Sinn der typisierten Schnittstelle: eine
noul-Frage ist keine choice mit zwei Optionen, die zufällig „ja” und „nein” lauten — sie ist ein
anderer Kopf mit einer anderen Ausgabeform, und der Typ sagt Laya, welchen es verwenden soll.
Die drei Typen
choice — eine aus einer Menge wählen
{"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {"billing": "money and invoices", "technical": "bugs and outages"}}
criteria ist ein geordnetes Mapping von Label zu Beschreibung. Die Reihenfolge ist positional in der
gerenderten Frage, zwei Fragen mit denselben Labels in anderer Reihenfolge sind also verschiedene Fragen.
Eine Beschreibung ist optional; {"billing": None} rendert nur das Label. Labels werden genau so
zurückgegeben, wie du sie geschrieben hast, ein Nicht-String-Label kommt also als es selbst in choice
und als der Schlüssel in probabilities zurück.
Die Beschreibung lohnt sich zu schreiben. Sie ist keine Dekoration: der gerenderte Fragetext ist das, was
das Modell liest, ein blankes {"a": None, "b": None} gibt ihm also nichts, um die Optionen zu
unterscheiden.
{"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 — ja oder nein
{"type": "noul", "instructions": "Is this urgent?"}
noul ist der Name des Projekts für eine
Zwei-Wege-Entscheidung, und seine Antwort ist die Wahrscheinlichkeit für wahr, kein schwellenwertiger
Boolescher Wert:
{"type": "noul", "noul": 0.8727, "confidence": 0.8727, "answer_confidence": 0.8727,
"action": {"act_probability": 1.0}}
Den Schwellenwert wählst du selbst, denn er hängt davon ab, was ein falsch positives Ergebnis dich kostet.
Es gibt kein bool-Feld, das man für eine Entscheidung halten könnte.
Du kannst die beiden Optionen umbenennen, wenn die Entscheidung nicht von Natur aus ein Ja/Nein ist —
labels nimmt genau die Schlüssel false und true, und die Polarität bleibt unverändert: noul ist
weiterhin P(true).
{"type": "noul", "instructions": "Does this need a human?",
"labels": {"false": "automatic", "true": "escalate"}}
Labels sind kein Weg, eine Frage zu reparieren, die das Modell falsch beantwortet. noul folgt seinen
eigenen Optionslabels, am stärksten auf dem englischen Checkpoint, ein Labelpaar, das als Entscheidung
gelesen wird („approve” / „reject”), kann die Antwort also eher zum Label als zum Zustand ziehen. Validiere
jede Umbenennung an deinen eigenen Daten, bevor du dich darauf verlässt.
score — ein geordnetes Niveau
{"type": "score", "instructions": "How severe is this?",
"criteria": ["trivial", "minor", "moderate", "serious", "critical"]}
criteria ist eine geordnete Liste, und sie muss aufsteigend geordnet sein — die Position ist die Skala.
{"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 ist ein Erwartungswert, nicht das wahrscheinlichste Niveau. Oben ist score 2.90, während das
einzelne wahrscheinlichste Niveau serious (3) mit 0.788 ist. Beide sind nützlich und beantworten
verschiedene Fragen: der Erwartungswert minimiert den quadratischen Fehler über die Skala, das Argmax
minimiert die Abweichung vom Modell. Wenn du das Label willst, nimm das Argmax von probabilities oder
lies das Gegenstück von answer_confidence aus der Legende — runde score nicht und nimm an, dass es das
Label ist. legend gibt es, damit du nie raten musst, welcher Index was bedeutet.
Konfidenz lesen
Jede Antwort trägt zwei Konfidenzzahlen, und sie messen Verschiedenes.
| Feld | Was es ist | Gating darauf? |
|---|---|---|
answer_confidence |
max(p) — die Wahrscheinlichkeit der gemeldeten Antwort |
ja, nach der Anpassung |
confidence |
1 - H(p) / log(k) — wie konzentriert die ganze Verteilung ist |
nein |
probabilities |
die vollständige Verteilung (choice, score) |
— |
answer_confidence ist die Größe, die das Temperaturskalieren anpasst und auf der jede Kalibrierungszahl
im Repository berechnet wird, was sie zu der Größe macht, auf die man Gating anwendet. Sie ist in der
ausgelieferten Form nicht kalibriert: die Eigenschaft, die ihr üblicherweise zugeschrieben wird — dass
von den Antworten, die mit Konfidenz c zurückgegeben werden, etwa c richtig sind — gilt erst, wenn die
Temperaturen angepasst und auf zurückgehaltenen Daten für deinen Checkpoint bei deiner Optionszahl
validiert wurden. Die ausgelieferten Checkpoints sind überkonfident, und wie stark, hängt von der
Optionszahl ab, ein unabgestimmter Schwellenwert kann also unter der eigenen Genauigkeit des Modells
auswählen (#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 ist die normierte Entropie: hoch, wenn die Verteilung spitz ist, niedrig, wenn sie breit
gestreut ist, unabhängig davon, ob die Top-Antwort richtig ist. Es ist ein nützliches Signal und nicht
auf derselben Skala, die beiden dürfen also nicht gegen dieselbe Zahl als Schwellenwert geprüft werden:
# 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
Für noul sind sie konstruktionsbedingt gleich — bei zwei Optionen ist max(p, 1-p) gleich max(p) —,
eine noul-Antwort kann dir also nicht sagen, welche der beiden du gerade gelesen hast. Beide Schlüssel
sind bei jedem Typ vorhanden, damit die Wahl explizit statt implizit ist.
Ein Schwellenwert ist eine Policy, nicht eine Eigenschaft des Modells. Beide Checkpoints werden
überkonfident ausgeliefert, und wie überkonfident, hängt von der Optionszahl ab, eine auf einer
3-Options-Frage gemessene Zahl überträgt sich also nicht auf eine mit 20. Miss ihn an deinen eigenen
Daten; der Abschnitt Kalibrierung in
BENCHMARKS.md enthält die
Anpassungsschleife und die angepassten Werte.
action und act_probability
action.act_probability ist der Score eines separaten Kopfs für „sollte ein Agent überhaupt darauf
reagieren”, verschieden von der eigenen Konfidenz der Antwort. Sie wird für jeden Fragetyp gemeldet.
Nichts in der Bibliothek legt dafür einen Schwellenwert für dich fest.
Presets
Drei fertige Fragensets, damit die gängigen Fälle keine handgeschriebenen criteria brauchen:
from laya import triage_questions, guard_questions, moderation_questions
agent.system_one(ticket, triage_questions())
Verwende sie als Ausgangspunkt statt als Vertrag — lies die Fragen, die sie erzeugen, mit
render_options und prüfe, ob die Labels zu deiner Domäne passen, bevor du sie auslieferst.
Die Optionen zurücklesen
Da die Optionsreihenfolge positional ist und der Optionstext das ist, was das Modell liest, lohnt es sich, genau sehen zu können, was gesendet wurde:
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']
Dieselben Labels in anderer Reihenfolge rendern in dieser Reihenfolge, weshalb die Reihenfolge Teil der Identität der Frage ist:
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']
Achte auf die Schlüssel. render_options nimmt die interne Kurzschlüssel-Form
{"t": ..., "crit": ...}, nicht die Form {"type": ..., "criteria": ...}, die du in einer Frage
schreibst — die öffentliche Form zu übergeben löst KeyError: 't' aus.
Die Umwandlung ist ein kurzes, stabiles Mapping, das du inline einfügen kannst, was vermeidet, in einen
privaten Helfer zu greifen — Agent._to_internal ist intern und kann sich ändern:
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))
Das spiegelt, was die Bibliothek für eine als Labelliste geschriebene choice-Frage tut; ein
criteria-Dict und eine score-Liste gehen unverändert durch.
Grenzen, die du kennen solltest, bevor du darum herum designst
- Konfidenz ist bei hohen Optionszahlen keine Korrektheitsgarantie. Bei einer 20-Options-Frage überlappen sich die Verteilungen für richtige und falsche Antworten stark, und ein Schwellenwert kann am Ende unter der eigenen Genauigkeit des Modells auswählen. Siehe #394.
- Negation wird bei erzwungener Wahl nicht zuverlässig behandelt. Eine Kündigungsfrage kann das Kündigungslabel für einen Zustand zurückgeben, der nicht kündigen sagt, mit hoher Konfidenz, auf beiden Checkpoints. Siehe #377.
noulkann seinen Labels statt dem Zustand folgen, validiere also jede Umbenennung.- Mehr Optionen sind nicht kostenlos. Ab etwa 20 verschlechtert sich das Modell schnell; nutze den Shortlist-Helfer, um einen großen Labelraum vor der Frage zu verkleinern, oder teile ihn in eine grobe und eine feine Frage auf.