Guides
Utilisateurs simulés
Une conversation scriptée teste votre assistant face à un utilisateur qui ne réagit jamais. Un utilisateur simulé réagit : un modèle joue une personne que décrit chaque cas, écrit chaque message suivant à partir de ce que votre assistant vient de dire, et s’arrête quand son objectif est atteint ou qu’il abandonne. Oloproof enregistre ce qui a circulé entre eux, le mesure comme n’importe quelle conversation, et ne laisse une porte décider dessus qu’une fois que des personnes ont jugé l’utilisateur simulé réaliste. Le tutoriel parcourt un service d’assistance qui parle avec des clients simulés.
Personas
L’entrée de chaque cas contient une persona : qui est l’utilisateur, ce qu’il veut et ce qu’il sait.
{"id": "c25", "input": {"persona": {
"description": "A retired teacher who ordered a cookbook last week. Writes short, polite messages.",
"goal": "Have the order of a cookbook delivered to my new address.",
"facts": {"order": "B-35", "postcode": "10275"}
}}}- description et goal sont obligatoires. Écrivez un objectif qu’un lecteur pourrait vérifier à partir de la seule conversation : « faire en sorte que la boutique lance un remboursement » peut être décidé d’après ce que dit l’assistant, « obtenir un remboursement » ne le peut pas, parce que l’argent arrive plus tard.
- facts est ce que l’utilisateur sait et ne partage que lorsque la conversation en a besoin, comme un numéro de commande. Votre assistant ne voit jamais la persona.
- opening est le premier message de l’utilisateur, mot pour mot, quand chaque système doit être accueilli de la même façon.
- max_turns plafonne les messages de cet utilisateur, en remplaçant celui du projet.
Une persona est du contenu du jeu de données : elle est couverte par l’empreinte de la suite et elle est la même pour chaque système mesuré face à elle.
Déclarer l’utilisateur simulé
Le modèle qui joue chaque persona se déclare sur le système, comme un juge :
system:
name: support-desk
callable: desk:talk
simulated_user:
provider: openai_compatible
base_url: http://localhost:11434/v1
model: qwen2.5vl
max_turns: 6Chaque champ est dans la référence de configuration. Un modèle local ne coûte rien ; un modèle hébergé facture chaque message qu’il écrit, et ses jetons sont enregistrés à côté de la conversation et jamais comptés comme coût de votre système.
Votre système garde sa boucle
Oloproof ne pilote pas la conversation. Votre système demande le premier message de l’utilisateur, y répond comme il répond aux utilisateurs, et rend chaque réponse pour obtenir le message suivant :
from oloproof import current_case
def talk(case):
user = current_case().simulated_user()
message = user.opening()
while message is not None:
message = user.reply(my_assistant.answer(message))
return {}reply renvoie None une fois que l’utilisateur s’arrête. Votre système peut mettre fin lui-même à la conversation avec user.end(answer), comme le ferait un transfert vers une personne ; un système async def attend aopening(), areply(answer) et aend(answer). Oloproof enregistre ce qui est passé par l’objet comme conversation/v1 et la manière dont elle s’est terminée comme simulated_user/v1, et refuse l’un comme l’autre venant de votre système (Référence du SDK).
Comment une conversation se termine
Après chacune des réponses de votre système, on demande au modèle de l’utilisateur simulé, par une question séparée à la température 0 et sans le jeu de rôle qui l’entoure, si cette réponse a accompli l’objectif. Si c’est le cas, la conversation se termine en goal_reached. Sinon l’utilisateur écrit son message suivant, ou s’en va (user_left), jusqu’à ce qu’il ait envoyé max_turns messages (turn_limit). Un système qui s’arrête sans répondre au dernier message laisse la conversation unanswered et tronquée.
C’est le fait de demander à part qui rend utilisable un petit modèle local : jouer une persona et décider si un objectif a été atteint sont des tâches différentes, et un modèle à qui l’on demande les deux dans une seule réponse a tendance à continuer de bavarder après que son objectif a été atteint, ou à dire qu’il s’en va alors qu’il est satisfait.
Mesurer les conversations
La conversation est l’unité : chaque évaluateur rend un verdict par conversation, si bien que chaque intervalle, comparaison et porte s’applique sans changement.
| Type | Réussit quand |
|---|---|
| conversation_completed | votre système a répondu à chaque message |
| goal_reached | la vérification de l’objectif a trouvé l’objectif atteint |
| turns_to_goal | il l’a été en au plus max_turns des messages de l’utilisateur |
| every_turn | chaque réponse remplit les conditions déclarées : des mots qu’elle doit contenir ou ne pas contenir, un motif, une longueur |
| conversation_judge | un juge modèle de toute la transcription la fait passer, une fois validé contre des étiquettes posées par des personnes |
Les répliques (replicates:) rencontrent des utilisateurs à graines différentes, si bien qu’une persona donne plusieurs conversations. La version de l’utilisateur simulé (son modèle, son prompt, sa température et son budget) fait partie de la clé de cache de chaque exécution, si bien qu’un utilisateur simulé différent mesure à nouveau votre système.
Valider l’utilisateur simulé
Un résultat obtenu sur des utilisateurs simulés ne vaut pour de vrais utilisateurs que dans la mesure où l’utilisateur simulé leur ressemble. Ainsi, chaque règle sur une exécution dont les utilisateurs étaient simulés est INSUFFICIENT_EVIDENCE avec simulated_user_not_validated jusqu’à ce que des personnes aient jugé ses conversations réalistes :
oloproof labels export RUN_ID --criterion simulated_user_realistic --sample 30 --local --out realism.csv
oloproof labels import realism.csv
oloproof simulated-users RUN_IDLa feuille montre chaque conversation tirée avec la persona et la manière dont elle s’est terminée. Une personne marque passed à oui quand un vrai client aurait pu écrire le côté de l’utilisateur et y mettre fin à ce moment-là, de cette manière, ce qui couvre aussi la vérification de l’objectif : une conversation terminée en goal_reached alors que l’objectif n’était pas atteint n’est pas réaliste. L’utilisateur simulé est validé sur la suite quand la borne inférieure exacte de la part réaliste franchit minimum_simulated_user_realism dans release.yaml. Une conversation tirée restée sans étiquette compte comme irréaliste, et une conversation n’est réaliste que si tous ceux qui l’ont étiquetée le disent.
Une validation couvre une version d’un utilisateur simulé sur une suite, quel que soit le système avec lequel il a parlé, si bien qu’un candidat mesuré face aux mêmes personas est décidé sur les étiquettes de la référence. Un nouveau modèle, un nouveau réglage du prompt ou un nouvel ensemble de personas demande de nouvelles étiquettes. Régler require_validated_evaluators: false décide sur des utilisateurs simulés sans elles, en connaissance de cause.
Simulé à côté du réel
oloproof simulated-users RUN_ID --real REAL_RUN_ID place la forme des conversations simulées à côté d’une exécution de conversations réelles (des discussions journalisées rejouées comme conversation/v1) : combien de messages l’utilisateur a envoyés, quelle était leur longueur, quelle était la longueur des réponses, à quelle fréquence une conversation a été interrompue, et comment les conversations simulées se sont terminées. C’est descriptif et cela ne décide jamais : comparer deux échantillons non appariés demande une méthode qu’Oloproof n’a pas admise. Lisez-le pour ce que les étiquettes de réalisme manquent, comme des utilisateurs simulés qui écrivent en une phrase bien rangée ce que de vrais utilisateurs étalent sur trois.
Non pris en charge
- Décider sur des utilisateurs simulés dans un espace de travail hébergé : ses décisions restent retenues tant que sa file de relecture ne peut pas tirer et sceller elle-même des étiquettes de réalisme.
- Une porte sur la manière dont les conversations simulées se comparent aux réelles.
- Des taux par tour au sein d’une conversation, et le fait de rejouer une conversation à partir d’un tour.
- Des utilisateurs simulés pour un système HTTP : c’est le code de votre propre système qui exécute la boucle, il doit donc être un appelable.