Choice
Ein Choice ist ein System One-Fragetyp, um eine Option aus einer definierten Menge auszuwählen. Die Antwort enthält die ausgewählte Option, eine Wahrscheinlichkeit für jede Option und die Konfidenz.
Verwende einen Choice, wenn die Antwort eine aus einer festen Menge von Optionen ist. Zum Beispiel, welches Team ein Ticket bearbeitet, zu welcher Kategorie ein Produkt gehört oder in welcher Sprache ein Codeausschnitt geschrieben ist. Wenn die Antwort eine Position auf einem Spektrum ist, verwende einen Score. Ist sie Ja oder Nein, verwende einen Noul. Einen Fragetyp wählen vergleicht alle drei.
Eine Choice-Antwort ist die ausgewählte Option in choice. Das Modell gibt außerdem eine Wahrscheinlichkeit für jede Option in probabilities und einen confidence-Wert für die ausgewählte Option zurück.
Beispielfragen:
"What programming language is this code written in"
→ options: python, javascript, typescript, go, rust, other
"What type of meeting is this based on the title and description"
→ options: standup, planning, retrospective, one on one, brainstorm, none of the above
"Which product category does this item belong to"
→ options: electronics, clothing, home garden, food and beverage
Anfragestruktur
Der POST-Anfragekörper an die TypeSafe-API hat eine bestimmte Struktur. Die oberste Ebene hat drei Felder: state, den auszuwertenden Inhalt; model; und questions, eine Zuordnung von selbst gewählten Frage-IDs zu Frageobjekten. Jede Choice-Frage hat die folgenden Felder:
type: Immer"choice".instructions: Die Frage, die das Modell beantwortet.criteria: Die Antwortoptionen als Zuordnung. Jeder Schlüssel ist ein Optionsname und jeder Wert eine Beschreibung dieser Option.
Unten steht eine Anfrage, bei der der Zustand ein Support-Ticket aus einem Online-Schuhshop ist und die Frage lautet, welches Team es bearbeiten soll:
{
"state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems"
}
}
}
}Du wählst die Frage-ID, in diesem Fall department. Die Antwort wird unter derselben ID zurückgegeben. Das Modell sieht die Frage-ID nie. Die Optionsnamen und ihre Beschreibungen werden beide an das Modell gesendet, schreibe also Beschreibungen, die die Optionen voneinander abgrenzen.
Unsere Client-SDKs bieten typisierte Fragen. In Python ist dieselbe Frage ein Choice:
from typesafe_sdk import Choice, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
questions={
"department": Choice(
instructions="Which team should handle this?",
criteria={
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems",
},
),
},
)
print(response.answers["department"].choice)
Verwende die Methode system_one oder den Endpunkt https://api.typesafe.ai/v1/systemone, um ein System One-Modell aufzurufen. Das Feld model wählt, welches Modell die Anfrage bearbeitet. Mit TypeSafe bauen behandelt, wo in deinem Code du es aufrufst.
Verwende eines unserer Client-SDKs oder rufe die HTTP-API direkt auf. Wenn ein Coding-Agent die Integration für dich schreibt, installiere zuerst den TypeSafe-Agent-Skill, damit er die Formen von Anfrage und Antwort kennt.
Antwortstruktur
Die Antwort hat einen Eintrag in answers pro Frage, unter den IDs aus der Anfrage. Dies ist die Antwort auf die Beispielanfrage oben:
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "returns",
"confidence": 1.0,
"probabilities": {
"shipping": 0.0,
"returns": 1.0,
"billing": 0.0
}
}
},
"usage": {
"input_tokens": 328,
"output_tokens": 34
}
}
Neben type hat jede Choice-Antwort drei Werte:
choice: Die Option mit der höchsten Wahrscheinlichkeit.probabilities: Die vollständige Wahrscheinlichkeitsverteilung über alle Optionen. Die Summe aller Werte ist 1.confidence: Eine Zahl von 0 bis 1, berechnet daraus, wieprobabilitiesverteilt ist. Eine flache Form, bei der sich die Wahrscheinlichkeit über mehrere Optionen verteilt, bedeutet niedrige Konfidenz. Ein einzelner Gipfel auf einer Option bedeutet hohe Konfidenz.
Dieses Ticket ist einfach, daher liegt die ganze Wahrscheinlichkeit auf returns und die Konfidenz beträgt 1.0. Ein Ticket, das eine falsche Größe und eine fehlende Erstattung erwähnt, würde die Wahrscheinlichkeit zwischen returns und billing aufteilen, und die Konfidenz würde sinken.
Gute Praxis: stelle mehr als eine Frage pro Aufruf
Stelle jede Choice-Frage, die dein Code brauchen könnte, in einer einzigen Anfrage statt einer Anfrage pro Frage. Fragen werden parallel ausgewertet. Fragen hinzuzufügen ändert die Antwortzeit kaum, und der Code kann Antworten ignorieren, die er nicht braucht. Zusätzliche Fragen kosten trotzdem Token. Mehrere Fragen zusammen stellen erklärt das vollständig; der nächste Abschnitt zeigt fünf Choice-Fragen in einem Aufruf.
Dieselbe Logik gilt für die Optionen innerhalb einer einzelnen Choice-Frage. Eine Choice-Frage akzeptiert bis zu 255 Optionen, und jede zusätzliche Option kostet ein paar Token, gib dem Modell also die vollständige Liste von Teams, Kategorien oder Produkten statt einer Auswahl. Füge eine Option other oder none of the above hinzu, wenn die Liste möglicherweise nicht jede Eingabe abdeckt, damit das Modell sagen kann, dass keine der anderen passt.
Um Dokumente durch eine tiefe Hierarchie oder große Taxonomie zu klassifizieren, verkette Choice-Fragen Ebene für Ebene. Das Cookbook zur hierarchischen Klassifikation zeigt, wie du eine Beam-Suche über Choice-Wahrscheinlichkeiten ausführst und auf jeder Ebene die besten K Kandidatenpfade behältst, statt dich auf einen einzigen gierigen Pfad festzulegen.
Ein komplexeres Beispiel
Das einfache Beispiel oben leitet ein Ticket an ein Team. Ein größeres Support-System könnte außerdem den Rückgabegrund, das Lieferproblem, den Wunsch des Kunden und den Ton des Kunden brauchen.
Die Anfrage unten stellt fünf Choice-Fragen zu einem Ticket, das mehrdeutiger ist als das erste: Es betrifft drei Teams und sagt nicht, was der Kunde will.
{
"state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges of $120 on my card. What are you going to do about this?",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems"
}
},
"return_reason": {
"type": "choice",
"instructions": "If the customer wants to return something, why?",
"criteria": {
"wrong_size": "The item doesn't fit",
"wrong_item": "A different product was delivered",
"damaged": "The item arrived broken or faulty",
"changed_mind": "The item is fine, the customer no longer wants it",
"other": "A return reason that fits none of the above"
}
},
"shipping_issue": {
"type": "choice",
"instructions": "If this is a shipping problem, which kind is it?",
"criteria": {
"not_delivered": "The package never arrived",
"delayed": "The package is late but still on its way",
"wrong_address": "The package went to the wrong place",
"damaged_in_transit": "The package arrived damaged",
"other": "A shipping problem that fits none of the above"
}
},
"requested_resolution": {
"type": "choice",
"instructions": "What does the customer want to happen?",
"criteria": {
"exchange": "Swap the item for a different one",
"refund": "Money back",
"replacement": "The same item sent again",
"information": "Just an answer, no action needed"
}
},
"tone": {
"type": "choice",
"instructions": "What is the customer's tone?",
"criteria": {
"calm": null,
"frustrated": null,
"angry": null
}
}
}
}Zwei dieser Choice-Fragen sind spekulativ: return_reason ist nur relevant, wenn department returns ist, und shipping_issue nur, wenn es shipping ist. Die Frage tone verwendet null-Beschreibungen, weil die Optionsnamen für sich genommen klar sind.
Die TypeSafe-Antwort:
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "returns",
"confidence": 0.42,
"probabilities": {
"shipping": 0.04,
"billing": 0.35,
"returns": 0.61
}
},
"return_reason": {
"type": "choice",
"choice": "wrong_size",
"confidence": 1.0,
"probabilities": {
"other": 0.0,
"wrong_size": 1.0,
"changed_mind": 0.0,
"damaged": 0.0,
"wrong_item": 0.0
}
},
"shipping_issue": {
"type": "choice",
"choice": "delayed",
"confidence": 0.67,
"probabilities": {
"wrong_address": 0.0,
"other": 0.26,
"not_delivered": 0.0,
"damaged_in_transit": 0.0,
"delayed": 0.74
}
},
"requested_resolution": {
"type": "choice",
"choice": "refund",
"confidence": 0.2,
"probabilities": {
"replacement": 0.34,
"refund": 0.4,
"information": 0.02,
"exchange": 0.24
}
},
"tone": {
"type": "choice",
"choice": "frustrated",
"confidence": 0.76,
"probabilities": {
"frustrated": 0.84,
"angry": 0.16,
"calm": 0.0
}
}
},
"usage": {
"input_tokens": 589,
"output_tokens": 212
}
}
Jede Frage wird für sich gegen das Ticket beantwortet:
- Die Antwort
departmentistreturnsmit einer Wahrscheinlichkeit von 0.61, aberbillinghat 0.35 wegen der doppelten Abbuchung. Das Ticket gehört zu zwei Teams, und die gespaltene Konfidenz von 0.42 spiegelt das wider. - Der
return_reasonistwrong_sizemit einer Konfidenz von 1.0, was zu erwarten ist, weil das Ticket das klar sagt. - Die Antwort
shipping_issueteilt sich zwischendelayedundother. Es ist eine spekulative Frage, unddepartmentkam nicht als shipping zurück, also kann der Code sie ignorieren, wie im Beispiel-Codeschnipsel unten gezeigt. - Die Antwort
requested_resolutionneigt zurefundmit 0.40, wobeireplacementundexchangeden größten Teil des Rests teilen, und die Konfidenz ist 0.20. Die doppelte Abbuchung deutet auf Geld zurück, die falsche Größe auf einen Umtausch, und der Kunde sagt nie, was er will. - Die Antwort
toneistfrustratedmit einer Wahrscheinlichkeit von 0.84 und einer Konfidenz von 0.76.
Der Beispielcode unten liest die Antworten, die er braucht, ignoriert den Rest und behandelt eine Antwort mit niedriger Konfidenz als Grund zu fragen statt zu handeln:
from typesafe_sdk import Choice, TypeSafeClient
TRIAGE_QUESTIONS = {
"department": Choice(
instructions="Which team should handle this?",
criteria={
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems",
},
),
"return_reason": Choice(
instructions="If the customer wants to return something, why?",
criteria={
"wrong_size": "The item doesn't fit",
"wrong_item": "A different product was delivered",
"damaged": "The item arrived broken or faulty",
"changed_mind": "The item is fine, the customer no longer wants it",
"other": "A return reason that fits none of the above",
},
),
"shipping_issue": Choice(
instructions="If this is a shipping problem, which kind is it?",
criteria={
"not_delivered": "The package never arrived",
"delayed": "The package is late but still on its way",
"wrong_address": "The package went to the wrong place",
"damaged_in_transit": "The package arrived damaged",
"other": "A shipping problem that fits none of the above",
},
),
"requested_resolution": Choice(
instructions="What does the customer want to happen?",
criteria={
"exchange": "Swap the item for a different one",
"refund": "Money back",
"replacement": "The same item sent again",
"information": "Just an answer, no action needed",
},
),
"tone": Choice(
instructions="What is the customer's tone?",
criteria={"calm": None, "frustrated": None, "angry": None},
),
}
def triage(ticket: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
state=ticket,
questions=TRIAGE_QUESTIONS,
)
answers = response.answers
department = answers["department"]
if department.confidence < 0.3:
# Not clear which team to send to. Let a person decide.
send_to_manual_triage(ticket)
return
if department.choice == "returns":
# return_reason answer is only used here
assign(ticket, team="returns", issue=answers["return_reason"].choice)
elif department.choice == "shipping":
# shipping_issue answer is only used here
assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
else:
assign(ticket, team="billing")
# A second team with a real share of the probability gets a copy
for team, probability in department.probabilities.items():
if team != department.choice and probability > 0.25:
notify(ticket, team=team)
resolution = answers["requested_resolution"]
if resolution.confidence < 0.5:
# The customer hasn't said what they want. Ask, don't guess.
ask_customer_what_they_want(ticket)
elif resolution.choice == "refund":
flag_for_refund_approval(ticket)
if answers["tone"].choice == "angry":
flag_for_senior_agent(ticket)
Für das Ticket oben weist dies das Ticket dem Returns-Team mit dem Problem wrong_size zu, schickt dem Billing-Team eine Kopie, weil sein Anteil von 0.35 über dem Schwellenwert 0.25 liegt, und fragt den Kunden, was er will, weil die Konfidenz der Lösung von 0.20 unter 0.5 liegt. Der Code verwendet die Antwort shipping_issue nicht.
Eine Anfrage, fünf Antworten, und die Routing-Logik sind gewöhnliche if-Anweisungen. Wenn du später die Sprache des Kunden wissen musst oder um welches Produkt es im Ticket geht, füge eine weitere Choice-Frage zu TRIAGE_QUESTIONS hinzu; die Anzahl der Anfragen bleibt bei eins.
Die Smart-Home-Assistant-Demo wertet jede Nutzeranfrage in einem Aufruf gegen eine lange Liste von Choice-Fragen aus: die Anforderungskategorie, den Raum, das Gerät und die Aktion. Die meisten dieser Fragen sind für eine einzelne Anfrage irrelevant, und der Code ignoriert sie.
Strukturierte Anweisungen und criteria
Beginne mit einer einzeiligen Beschreibung pro Option. Wenn zwei Optionen ähnlich sind und das Modell sie immer wieder verwechselt, beschreibe jede mit einem Objekt statt einer Zeichenkette. Gib ihm Felder dafür, was die Option abdeckt, was stattdessen zu einer benachbarten Option gehört, und ein paar Beispieleingaben.
Die beiden Antwortoptionen unten, return_policy und return_status, sind leicht zu verwechseln. Ein Ticket über eine von beiden kann Returns und Erstattungen erwähnen, daher sagt jede Option, wofür sie nicht gedacht ist.
{
"state": "I sent the shoes back a week ago. When do I get my money?",
"questions": {
"return_topic": {
"type": "choice",
"instructions": {
"question": "Which returns topic is the customer asking about?",
"focus": "Classify the information the customer wants."
},
"criteria": {
"return_policy": {
"what": "Whether and how an item can be returned",
"not_for": "Progress of a return already sent",
"examples": [
"Can I return shoes I've worn once?",
"How long do I have to return an order?"
]
},
"return_status": {
"what": "Progress of a return already sent",
"not_for": "Whether and how an item can be returned",
"examples": [
"Has my return arrived yet?",
"When will my refund be paid?"
]
}
}
}
}
}Die Antwort ist return_status mit Konfidenz 1.0:
{
"model": "jev-1.13.0",
"answers": {
"return_topic": {
"type": "choice",
"choice": "return_status",
"confidence": 1.0,
"probabilities": {
"return_policy": 0.0,
"return_status": 1.0
}
}
},
"usage": {
"input_tokens": 407,
"output_tokens": 32
}
}
Die Feldnamen question, focus, what, not_for und examples sind nicht Teil der API, und keiner ist reserviert. Du wählst sie, genauso wie du Optionsnamen wählst. Das Modell sieht die Namen zusammen mit den Werten, verwende also kurze Namen, die benennen, was folgt.