Noul
Une question Noul demande au modèle TypeSafe d’évaluer une question oui/non et de renvoyer la probabilité que la réponse soit oui.
Utilise un Noul quand la réponse est oui ou non. Par exemple, ce message demande-t-il un remboursement, ce CV mentionne-t-il des systèmes distribués, ce commentaire contient-il des données personnelles. Si la réponse est l’une de plusieurs options, utilise un Choice. Si c’est une position sur un spectre, utilise un Score. Choisis un type de question compare les trois.
Une réponse Noul est un seul nombre représentant la probabilité que la réponse soit oui, où 0 signifie non et 1 signifie oui.
Structure de la requête
Le corps de la requête POST vers l’API TypeSafe a les mêmes trois champs de niveau supérieur que tout autre type de question : state, qui est le contenu à évaluer ; model ; et questions. Chaque question Noul a les champs suivants :
type: toujours"noul".instructions: la question oui/non à laquelle le modèle répond, ou une affirmation qu’il doit juger.criteria: facultatif. Un objet avec des descriptionstrueetfalsede ce que signifient un oui et un non.
Voici une requête où l’état est un message de support et les deux questions sont si le client veut une personne et s’il a déjà contacté le support :
{
"state": "I have asked three times now. Can I please just talk to a real person?",
"questions": {
"is_human_escalation": {
"type": "noul",
"instructions": "Is the customer asking for a human agent?"
},
"is_repeat_contact": {
"type": "noul",
"instructions": "Has the customer contacted support about this before?",
"criteria": {
"true": "Mentions a prior attempt, ticket, or that they have asked before",
"false": "No sign of any previous contact"
}
}
}
}Tu choisis les ids de question, is_human_escalation et is_repeat_contact ici. Les ids ne sont pas envoyés au modèle. Chaque réponse est renvoyée sous le même id. La première question repose sur les seules instructions. La seconde ajoute criteria pour dire ce qui compte comme un oui et ce qui compte comme un non.
Avec le SDK Python, les mêmes questions sont des objets Noul :
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state="I have asked three times now. Can I please just talk to a real person?",
questions={
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
},
)
print(response.answers["is_human_escalation"].noul)
print(response.answers["is_repeat_contact"].noul)
La méthode system_one et l’endpoint https://api.typesafe.ai/v1/systemone portent tous deux le nom de System One, le modèle d’IA de TypeSafe. Construire avec TypeSafe indique où l’utiliser dans ton code.
Si tu utilises un agent de codage, installe d’abord le skill d’agent TypeSafe pour qu’il connaisse les formes de la requête et de la réponse.
Structure de la réponse
La réponse a une entrée dans answers par question, sous les ids de la requête :
{
"model": "jev-1.13.0",
"answers": {
"is_human_escalation": {
"type": "noul",
"noul": 0.99
},
"is_repeat_contact": {
"type": "noul",
"noul": 0.93
}
},
"usage": {
"input_tokens": 360,
"output_tokens": 39
}
}
Les deux réponses ici sont proches de 1. Le client dit « parler à une vraie personne », donc is_human_escalation vaut 0.99. « J’ai déjà demandé trois fois » correspond à la description true de is_repeat_contact, donc il vaut 0.93.
Lire un Noul
Le nombre est à la fois la réponse et la certitude. Une valeur proche de 1 est un oui franc. Une valeur proche de 0 est un non franc. Une valeur proche de 0.5 signifie que le modèle donne au oui et au non une probabilité similaire.
Le tableau ci-dessous montre des réponses jev-1.13.0 enregistrées à la question is_human_escalation pour différents messages client :
| État | noul |
|---|---|
| Merci, ça a réglé le problème ! | 0.02 |
| Comment réinitialiser mon mot de passe ? | 0.07 |
| Il me faut une solution aujourd’hui, quoi qu’il en coûte. | 0.26 |
| Es-tu un bot ? | 0.40 |
| Y a-t-il un moyen de parler à quelqu’un au sujet de ma facture ? | 0.84 |
| J’ai déjà demandé trois fois. Puis-je parler à une vraie personne, s’il vous plaît ? | 0.99 |
Les deux premières et les deux dernières sont claires. « Il me faut une solution aujourd’hui » est urgent mais ne demande jamais une personne, et obtient 0.26. « Es-tu un bot ? » laisse entendre qu’on veut un humain sans le demander, et le modèle se partage presque à parts égales à 0.40. Les deux sont le genre de message où une décision doit être prise à partir d’un seuil dans ton code.
Il n’y a pas de valeur confidence séparée pour un Noul, contrairement à un Choice ou à un Score. La distribution de probabilité d’un Noul n’a que deux issues, oui et non, donc l’unique valeur noul la décrit complètement. Un Choice ou un Score répartit la probabilité sur plusieurs options ou niveaux, et confidence résume cette répartition.
Le plus souvent, ton code transforme noul en booléen via un seuil :
wants_human = response.answers["is_human_escalation"].noul > 0.9
if wants_human:
route_to_agent(ticket)
else:
route_to_bot(ticket)
L’endroit où fixer le seuil dépend du coût d’une erreur. Utilise 0.5 quand agir sur un oui ou sur un non est aussi facile. Remonte-le quand agir sur un faux oui coûte cher, comme alerter quelqu’un ou émettre un remboursement. Abaisse-le quand laisser passer un vrai oui coûte cher, comme ne pas signaler un problème de sécurité. Les valeurs intermédiaires peuvent aller à une personne plutôt qu’à l’une ou l’autre branche du code. C’est la même division en trois voies que décrit la page Confiance pour les réponses Choice et Score.
Une valeur Noul va de 0 à 1, mais ce n’est pas une échelle de la chose sur laquelle tu as posé la question. C’est la probabilité que la réponse soit oui. Si la question porte en réalité sur un degré, la valeur ne mesure pas le degré. Ci-dessous, « Le candidat est-il fort en Python ? » est posée sur quatre candidats, à côté d’un Score à quatre niveaux : aucune expérience, une certaine familiarité, un usage régulier en emploi, une expertise profonde.
| Candidat | Noul : « Le candidat est-il fort en Python ? » | Score : « Quelle expérience de Python le candidat a-t-il ? » |
|---|---|---|
| Mon expérience est en Java et Go. Je n’ai pas utilisé Python. | 0.03 | 0.0 (Aucune expérience) |
| J’ai utilisé Python occasionnellement pour de petits scripts à côté de mon travail principal en Java. | 0.14 | 1.0 (Une certaine familiarité) |
| J’ai utilisé Python tous les jours pendant deux ans dans mon dernier emploi, surtout des pipelines de données. | 0.81 | 2.05 (Usage régulier en emploi) |
| J’écris du Python quotidiennement depuis huit ans, y compris la maintenance d’une grande base de code Django. | 0.92 | 2.89 (Expertise profonde) |
Le Noul juge une seule proposition, « fort », et les valeurs indiquent sa probabilité. Tu pourrais créer des niveaux dans l’intervalle 0 à 1 dans ton code, comme 0.3 à 0.7 pour « une certaine expérience », mais le modèle ne les verra pas, donc rien dans la réponse n’a été jugé par rapport à eux. Une valeur intermédiaire peut signifier une expérience moyenne ou un cas peu clair, et l’espacement entre les candidats n’est pas quelque chose que tu as choisi. Le Score juge chaque description de niveau pour elle-même, donc chaque candidat est tombé sur un niveau que tu as écrit ou près de lui, et les probabilités renvoyées montrent comment le modèle a réparti son jugement entre les niveaux. Si tu n’es pas d’accord, reformule un niveau et relance. Choisis un type de question explique la différence.
Écrire une question Noul
Pose une question oui/non par Noul. Si une question a deux conditions, comme « Le client est-il en colère et demande-t-il un remboursement ? », le modèle doit juger les deux à la fois et la valeur signifie moins. Pose deux Nouls et combine-les en code.
Formule la question de sorte qu’une valeur élevée signifie oui. « Le message contient-il des données personnelles ? » est clair. « Le message est-il exempt de données personnelles ? » inverse le sens, et le code qui le lira plus tard l’interprétera à l’envers.
Une affirmation marche aussi bien qu’une question. Pour « Le client demande un remboursement », une valeur proche de 1 signifie que l’affirmation est vraie. Essaie les deux formulations sur tes propres données pour voir laquelle marche le mieux.
Rends la frontière entre oui et non sans ambiguïté. « Ce candidat a-t-il la moindre expérience de Python ? » marche bien parce que « la moindre » ne laisse pas de terrain d’entre-deux. Quand la frontière est subtile, ajoute criteria avec des descriptions true et false, comme le fait la question is_repeat_contact ci-dessus. L’instruction suffit pour la plupart des Nouls, alors essaie tes questions avec et sans criteria et garde celle qui donne les meilleures réponses sur tes documents.
Bonne pratique : pose plusieurs questions par appel
Pour une liste de conditions à vérifier, pose beaucoup de questions Noul en une seule requête : une question par condition, et le code décide ce que la combinaison signifie. Les questions sont évaluées en parallèle, donc ajouter des Nouls ne change presque pas le temps de réponse. Poser plusieurs questions ensemble l’explique plus en détail.
Gérer plusieurs réponses Noul dans le code
La requête à deux questions ci-dessus donne au code de quoi router le message. L’exemple ci-dessous escalade vers une personne quand le client en demande une, et augmente la priorité quand il a déjà été en contact. Une valeur intermédiaire sur l’une ou l’autre question va à un relecteur au lieu d’une branche de code :
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
SUPPORT_QUESTIONS = {
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
}
YES = 0.8
NO = 0.2
def route(message: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state=message,
questions=SUPPORT_QUESTIONS,
)
answers = response.answers
wants_human = answers["is_human_escalation"].noul
repeat = answers["is_repeat_contact"].noul
if NO < wants_human < YES or NO < repeat < YES:
# The model isn't sure either way. Let a person decide.
send_to_review(message)
return
priority = "high" if repeat > YES else "normal"
if wants_human > YES:
route_to_agent(message, priority=priority)
else:
route_to_bot(message, priority=priority)
Pour le message ci-dessus, la valeur de réponse noul de is_human_escalation est 0.99 et celle de is_repeat_contact est 0.93, donc le code le route vers un agent en priorité haute. Le message « Comment réinitialiser mon mot de passe ? » vaut 0.07 sur les deux questions et est routé vers le bot.
Les seuils vivent dans ton code. Si les relecteurs voient trop de messages, resserre l’écart entre NO et YES. Si trop de mauvais routages passent, élargis-le. Si tu as ensuite besoin de savoir si le message mentionne un paiement, ou s’il contient des données personnelles, ajoute un autre Noul à SUPPORT_QUESTIONS. Le nombre de requêtes reste à un.
Instructions structurées
Les instructions peuvent être un objet au lieu d’une chaîne, avec la question dans un champ et des données supplémentaires dans les autres. Utiliser la structure dans les questions explique quand cela aide. Ici, c’est utilisé pour une question construite en code : un CV qui vient d’arriver est comparé à des enregistrements d’une base de candidats qui pourraient être la même personne. Chaque enregistrement va tel quel dans un champ potential_duplicate, la question est la même pour chaque enregistrement, et tous les enregistrements sont vérifiés en une seule requête. Les clés de question générées par le code contiennent l’ID de base de données de chaque enregistrement :
{
"state": {
"resume": {
"name": "John Smith",
"location": "Oakland, CA",
"summary": "Backend engineer with eight years of Python and Go experience.",
"experience": [
{
"employer": "Google",
"title": "Senior Backend Engineer",
"years": "2021-2025"
},
{
"employer": "Microsoft",
"title": "Software Engineer",
"years": "2017-2021"
}
]
}
},
"questions": {
"same_as_record_18": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "Jon Smith",
"location": "Oakland, CA",
"last_employer": "Google"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
},
"same_as_record_42": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "John Smith",
"location": "Austin, TX",
"last_employer": "Lone Star Freight"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
},
"same_as_record_77": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "John Smithers",
"location": "Oakland, CA",
"last_employer": "Bay Health Clinic"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
}
}
}La réponse :
{
"model": "jev-1.13.0",
"answers": {
"same_as_record_18": {
"type": "noul",
"noul": 0.74
},
"same_as_record_42": {
"type": "noul",
"noul": 0.09
},
"same_as_record_77": {
"type": "noul",
"noul": 0.08
}
},
"usage": {
"input_tokens": 535,
"output_tokens": 58
}
}
Chaque réponse est la probabilité que le CV soit celui de la personne de cet enregistrement. L’enregistrement 18 orthographie le nom différemment mais correspond sur le lieu et l’employeur, et obtient 0.74. L’enregistrement 42 a le même nom dans une autre ville avec un autre employeur, et obtient 0.09. L’enregistrement 77 est un nom similaire au même endroit avec un autre employeur, et obtient 0.08. Applique un seuil à chaque valeur dans ton code, comme dans Gérer plusieurs réponses Noul dans le code, et envoie les valeurs intermédiaires à une personne.
Avec le SDK Python, les questions sont construites à partir des enregistrements candidats. Le texte de la question est fixe et l’enregistrement change :
from typesafe_sdk import Noul, TypeSafeClient
SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"
def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
"""One Noul per candidate record, all asking the same question."""
return {
f"same_as_record_{candidate['id']}": Noul(
instructions={
"potential_duplicate": {
"name": candidate["name"],
"location": candidate["location"],
"last_employer": candidate["last_employer"],
},
"question": SAME_PERSON,
},
)
for candidate in candidates
}
def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state={"resume": resume},
questions=duplicate_questions(candidates),
)
return [
question_id
for question_id, answer in response.answers.items()
if answer.noul > 0.7
]
Le cookbook Cascade SDE utilise des instructions structurées pour vérifier un enregistrement extrait. Chaque champ reçoit le même ensemble de questions. L’objet instructions de chaque question contient le texte de la question dans la propriété main_question. Il y a aussi des propriétés field_spec et extracted_field qui changent pour chaque champ.
Noul dans les cookbooks
Jette un œil à nos cookbooks pour voir des applications qui utilisent des questions Noul :
- Questions en parallèle exécute une liste de contrôle réglementaire de 13 questions sur un article en une seule requête.
- Autocohérence : nouls évalue une déclaration d’assurance contre une grille de 15 questions et mesure la stabilité des valeurs d’une exécution à l’autre.
- Réordonnancement utilise la probabilité elle-même, pas un seuil : un Noul par paire requête-candidat, puis trie les candidats par la valeur.
- Recherche ligne par ligne associe un Choice qui trouve la ligne correspondante à un Noul qui vérifie si le document contient une réponse du tout.
- Récupération de structure pose un Noul par paire de lignes, pour savoir si un saut de ligne a coupé une phrase, afin de reconstruire des paragraphes à partir de texte brut.