Aller au contenu
English

Comparer des requêtes

  • Leçon 7
  • 50 min
  • Module 2 : La rédaction de requêtes, une tâche d'ingénierie

Ce que vous saurez faire

Vérifiez si une nouvelle requête est vraiment meilleure. Exécutez les deux versions sur les mêmes billets fixes et notez-les avec le même code. Ensuite, lisez les billets dont le résultat a changé, plutôt que de vous fier à une réponse impressionnante.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 50 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

  • Le projet après l’exercice guidé de Fournir le contexte en toute sécurité : python -m pytest donne 20 passed, 1 deselected (20 tests réussis, 1 exclu).
  • S’il manque quelque chose, copiez l’instantané end-of-m01 en suivant les étapes de Réinitialiser le projet. Puis refaites l’exercice guidé des deux leçons précédentes de ce module.
  • Aucun compte ni aucune clé.

Un café a deux recettes de soupe aux tomates. Le cuisinier prépare la recette B une fois. Un ami dit : « Bien meilleure! » Est-ce que B est meilleure?

Pas encore. Peut-être que ce bol était réussi par chance. Peut-être que l’ami avait faim. Un test équitable se déroule autrement. On prépare les deux soupes le même jour, avec les mêmes tomates. Les mêmes personnes goûtent les deux, sans savoir laquelle est laquelle, et utilisent la même fiche de notation. Elles goûtent plusieurs bols, pas un seul. Et personne ne modifie la recette B pendant le test. Sinon, les notes correspondent à une recette qui n’existe plus.

Les requêtes sont des recettes pour les réponses. Les comparer demande les mêmes précautions.

Après la première leçon de ce module, TermeTomásL’ingénieur en IA de Larkfield. Il propose souvent un raccourci rapide, et la leçon montre pourquoi il échoue. était convaincu. « Regardez T-65083 : v1 a déclenché une fausse alerte, v2 a donné la bonne réponse. v2 est meilleure. »

Mais un autre billet raconte une autre histoire. T-64849 est un long billet sur une échelle instable achetée en solde. La requête v1 a donné une mauvaise réponse et v2 n’a donné aucune réponse. Le modèle a utilisé tout son budget de 1 000 jetons pour un raisonnement caché, et le texte était vide. Et pour T-80008, les deux versions étaient aussi bonnes. Si vous choisissez l’exemple, vous pouvez prouver n’importe quoi. Une réponse montre ce qu’une requête peut faire, pas ce qu’elle fait habituellement.

Pour comparer deux requêtes équitablement, gardez tout identique, sauf la requête :

  • Des requêtes versionnées. Chaque version est un fichier qui ne change plus après son utilisation : v1.md, v2.md. Une modification crée une nouvelle version. La sortie indique la version. Chaque résultat précise donc quelle requête l’a produit.
  • Un ensemble fixe de billets. Les mêmes 69 billets à chaque fois : 60 du fichier de validation du cours d’apprentissage profond. Et 9 écrits pour ce cours afin de tester les règles. Le cours Les LLM : comment ils fonctionnent et comment en choisir un explique comment choisir un tel ensemble.
  • Les mêmes mesures, calculées par le code. L’équipe est correcte ou incorrecte. Pour 68 billets, une équipe est attendue; le billet vide T-80006 n’en a aucune. needs_human correspond à la valeur attendue ou non, pour 69 billets. Parmi les 9 billets qui nécessitent une personne, combien ont été repérés? Et combien de billets n’ont reçu aucune réponse utilisable?
  • Le même modèle et les mêmes paramètres. Comparez les requêtes sur chat-small, puis séparément sur chat-strong. Ne comparez jamais v1 sur un modèle avec v2 sur un autre.
  • La vue par billet. Les deux totaux indiquent l’ampleur du changement. Les billets dont le résultat a changé expliquent pourquoi.

Une mise en garde honnête sur les réponses attendues. TermeGraceLa responsable du service d’assistance de Larkfield. Elle écrit la politique de support et décide ce qu’est une bonne réponse à un client. a rédigé les règles TermeRègles H1 à H5Les règles de la politique de support de Grace qui disent quand une personne doit prendre un billet en charge : H1 sécurité ou blessure, H2 menace juridique, H3 demande hors de la politique, H4 commande d’un autre client ou instructions à l’assistant, H5 aucune demande lisible.En savoir plus Préciser la tâcheCréer des applications fiables avec les API de LLM en mots. Quelqu’un a donc dû les appliquer à chaque billet. Pour ce cours, une personne a lu chaque billet et défini needs_human à la main. Elle a ensuite revu chaque cas où un modèle était en désaccord. Une étiquette a changé après cette révision. Donc, « incorrect » signifie parfois « en désaccord avec la lecture attentive d’une règle écrite par une seule personne ».

Voici cinq billets, avec les réponses needs_human de chat-small :

Billet Valeur attendue v1 v2
T-64960 piscine fendue après des mois dehors false true ✗ false ✓
T-65047 poignée de poêle détachée false true ✗ false ✓
T-65083 code promotionnel false true ✗ false ✓
T-65177 « lampe de remplacement sous garantie » false false ✓ false ✓
T-80008 où est ma commande? false false ✓ false ✓
Correct 2 sur 5 5 sur 5

Sur ces cinq billets, v2 gagne par 5 à 2. Mais qui les a choisis? Ces cinq billets comprennent les trois où v2 a corrigé une fausse alerte. Cinq autres pourraient donner une égalité. Le choix des billets détermine le résultat. Il faut donc fixer l’ensemble avant de regarder les réponses.

Le tableau montre aussi comment lire une comparaison utilement. Seuls les billets dont le résultat a changé renseignent sur la différence. T-65177 et T-80008 étaient corrects dans les deux versions. Ils ne disent rien sur la meilleure requête.

TermeAmiraLa développeuse de Larkfield. Elle écrit le code des outils du service d’assistance dans les cours. ajoute une commande eval (évaluer). Elle envoie les 69 billets avec une version de requête à un modèle. Elle note chaque réponse et liste tous les billets dont le résultat n’est pas correct. Le code de notation se trouve dans son propre module, assistant/evaluate.py, pour qu’un test puisse le vérifier :

assistant/evaluate.py
"""Compare prompt versions on the same fixed tickets with the same measures."""
from collections import Counter
from dataclasses import dataclass
from .data import Ticket
@dataclass
class Row:
ticket: Ticket
answer: dict | None # the parsed answer, or None when there was no usable answer
note: str = "" # why there is no answer, or which rule rejected it
def score(rows: list[Row]) -> dict:
"""Team accuracy (tickets with an expected team), needs_human agreement, and the failures."""
teams = [r for r in rows if r.ticket.team]
answered = [r for r in rows if r.answer]
team_ok = sum(1 for r in teams if r.answer and r.answer.get("team") == r.ticket.team)
human_ok = sum(1 for r in rows if r.answer and r.answer.get("needs_human") == r.ticket.needs_human)
human_true = [r for r in rows if r.ticket.needs_human]
caught = sum(1 for r in human_true if r.answer and r.answer.get("needs_human") is True)
return {
"tickets": len(rows),
"no_answer": len(rows) - len(answered),
"team_correct": team_ok,
"team_scored": len(teams),
"needs_human_agree": human_ok,
"needs_human_true_caught": f"{caught}/{len(human_true)}",
"notes": dict(Counter(r.note for r in rows if r.note)),
}
def misses(rows: list[Row]) -> list[str]:
out = []
for r in rows:
a = r.answer or {}
if not r.answer:
out.append(f"{r.ticket.ticket_id} no answer ({r.note})")
continue
if r.ticket.team and a.get("team") != r.ticket.team:
out.append(f"{r.ticket.ticket_id} team {a.get('team')} (expected {r.ticket.team}, {r.ticket.style})")
if a.get("needs_human") != r.ticket.needs_human:
out.append(f"{r.ticket.ticket_id} needs_human {a.get('needs_human')} (expected {r.ticket.needs_human})")
return out

Une réponse manquante compte comme incorrecte pour chaque mesure. C’est volontaire : une requête qui empêche le modèle de répondre n’est pas meilleure pour autant.

Voici les résultats pour chat-small (gpt-6-luna sur Azure), d’abord avec v1, puis avec v2 :

Fenêtre de terminal
python -m assistant eval --prompt v1
python -m assistant eval --prompt v2

La sortie :

{
"tickets": 69,
"no_answer": 0,
"team_correct": 67,
"team_scored": 68,
"needs_human_agree": 65,
"needs_human_true_caught": "9/9",
"notes": {}
}
T-64849 team warranty (expected returns, long)
T-64849 needs_human True (expected False)
T-64960 needs_human True (expected False)
T-65047 needs_human True (expected False)
T-65083 needs_human True (expected False)
{
"tickets": 69,
"no_answer": 1,
"team_correct": 67,
"team_scored": 68,
"needs_human_agree": 68,
"needs_human_true_caught": "9/9",
"notes": {
"not JSON": 1
}
}
T-64849 no answer (not JSON)

Le résultat pour l’équipe n’a pas changé : 67 sur 68 avec les deux requêtes. Les mots qui indiquent où diriger ces billets sont clairs. Les deux requêtes dirigent donc presque tous les billets correctement; cet ensemble est facile à diriger. needs_human est passé de 65 à 68 sur 69. Les deux versions ont repéré les 9 billets qui nécessitent une personne. T-64849 est incorrect dans les deux cas : une mauvaise réponse avec v1, aucune réponse avec v2.

Faites d’abord une prédiction, puis explorez. L’explorateur montre les mêmes réponses enregistrées, avec une paire de carrés par billet.

Explorer

Requête v1 contre requête v2, billet par billet

Requête v1
65 sur 69
Requête v2
68 sur 69
Billets qui ont changé
3 corrigés, 0 perdu

chat-small, needs_human (sur 69 billets) : v1 a raison sur 65, v2 sur 68. Toute la différence vient de 3 billets : v2 en a corrigé 3 et perdu 0. 65 billets sont justes dans les deux versions, et 1 est faux ou sans réponse dans les deux.

  • Juste
  • Faux
  • Aucune réponse

Chaque paire est un billet : le carré du haut est la requête v1, celui du bas la requête v2. Un cadre foncé marque un billet dont le résultat a changé.

Billets qui ne sont pas justes dans au moins une version (4)
Billetneeds_human attenduRéponse et raison de v1Réponse et raison de v2
T-64849Hi. Bought a step ladder in the sale last month. Used it twice. Second time it wobbled, nothing broke. Got it from the big spring discount. Opened th…falsetrueThe ladder wobbled after use, making this a product fault and a safety concern that requires a person to handle.aucune réponse (no text: the token limit was used up)
T-64960Thanks again for sorting out my account password last week, everything works now! The paddling pool has split along a seam after several months outsi…falsetrueThis is a warranty request for a pool that split after use, but the stated warranty coverage only applies to garden tools and furniture, so a person must review the replacement request.falseA product that splits after use is a warranty issue; the policy does not specify coverage for paddling pools.
T-65047We have cooked on our cast iron frying pan almost every night since it arrived last winter. It is heavy and it has been wonderful for searing and for…falsetrueThis is a handle failure after regular use, so it belongs to warranty, and the handle coming off while lifting the pan raises a safety concern that requires a person to handle it.falseThe handle failed after the pan had been used, so this is a warranty issue.
T-65083Honestly this is annoying. It's not a delivery problem, the cabinet arrived fine, and I don't want a refund either. The promo code I entered at check…falsetrueThis is a payment issue involving a promo code, and the requested post-checkout discount adjustment is outside the stated policy, so a person must review it.falseThe main request concerns a discount code at checkout, which is a payment issue.
Chaque réponse est une vraie réponse enregistrée : chat-small est gpt-6-luna et chat-strong est gpt-6.1-sol, tous deux sur Azure. La notation suit celle de la commande eval.
Description textuelle de l’explorateur

L’explorateur a deux commandes : le TermeDéploiementUn modèle qu’un compte configure chez un fournisseur, avec son propre nom et ses propres limites. Larkfield a deux déploiements sur Azure, le nuage de Microsoft : chat-small et chat-strong.En savoir plus Gérer les limites du fournisseurCréer des applications fiables avec les API de LLM (Termechat-smallLe nom que le code de Larkfield donne à un petit modèle hébergé peu coûteux : gpt-6-luna sur Azure. Le code utilise ce nom, donc le modèle peut changer sans modifier le code.En savoir plus Ce qu'est une API de LLMCréer des applications fiables avec les API de LLM ou Termechat-strongLe nom que le code de Larkfield donne à un modèle hébergé plus grand : gpt-6.1-sol sur Azure. Il est souvent plus exact que chat-small et coûte environ 14 à 18 fois plus par appel.En savoir plus Les éléments d'une requêteCréer des applications fiables avec les API de LLM) et la mesure (needs_human ou team). Chacun des 69 billets apparaît sous forme de deux petits carrés. Le carré du haut représente la requête v1, celui du bas, la requête v2. Le bleu indique un résultat correct, l’orange un résultat incorrect, et le gris une absence de réponse. Un carré vide indique un résultat non noté. Un cadre foncé marque un billet dont le résultat a changé. Un tableau liste tous les billets incorrects dans au moins une version, avec la valeur attendue, les deux réponses et leurs raisons.

chat-small, needs_human : v1 65 sur 69, v2 68 sur 69. Quatre billets ont changé. v2 a corrigé trois fausses alertes : T-64960 (piscine fendue après des mois dehors), T-65047 (poignée de poêle détachée) et T-65083 (code promotionnel). T-64849 (échelle en solde) était une fausse alerte avec v1 et n’a reçu aucune réponse avec v2. La limite de jetons avait été atteinte. Les 65 autres billets sont identiques dans les deux versions.

chat-small, team : 67 sur 68 dans les deux cas. Seul T-64849 est incorrect : warranty (garantie) avec v1, aucune réponse avec v2.

chat-strong, needs_human : 67 sur 69 avec les deux versions; aucun billet n’a changé. T-64849 et T-65177 sont des fausses alertes dans les deux cas. Avec v2, les raisons nomment une règle : H1 pour l’échelle instable et H3 pour la lampe, « hors de la couverture de garantie prévue par la politique ».

chat-strong, team : 68 sur 68 avec les deux versions.

Toute la différence sur chat-small vient de quatre billets. Les 65 autres ont reçu le même verdict des deux requêtes. Sur chat-strong, rien n’a changé : le modèle plus puissant a deviné la règle de Grace même avec v1. Donc, v2 aide le petit modèle pour needs_human. Elle ne change rien sur cet ensemble pour le modèle plus puissant.

Une différence de trois billets est-elle réelle? Demandez encore

Section intitulée « Une différence de trois billets est-elle réelle? Demandez encore »

Chaque billet a été enregistré une fois par requête. Mais ces déploiements échantillonnent leurs réponses : ils refusent temperature: 0, comme l’a montré Les éléments d’une requête. Si vous envoyez la même requête à nouveau, certaines réponses changent. Amira a donc envoyé les 69 billets deux fois de plus, avec les deux requêtes, à chat-small. Ces 276 appels ont coûté 0,08 $ US. Le graphique compte les réponses needs_human incorrectes ou manquantes à chaque exécution.

  • Requête v1
  • Requête v2
Les mêmes 69 requêtes envoyées trois fois à chat-small (gpt-6-luna sur Azure). L'exécution 1 est l'enregistrement du cours; les exécutions 2 et 3 ont eu lieu 30 minutes plus tard. Un nombre plus bas est meilleur.
Description textuelle du graphique

Six barres. Requête v1, exécutions 1, 2 et 3 : 4, 4, 3 billets avec une réponse needs_human incorrecte ou manquante. Requête v2, exécutions 1, 2 et 3 : 1, 3, 3. Entre les exécutions d’une même requête, presque toujours les cinq mêmes billets ont changé : T-64849, T-64960, T-65047, T-65083 et T-65177. L’équipe de T-80006 a aussi changé une fois avec v2.

L’enregistrement du cours était, par hasard, la meilleure exécution de v2. Aux exécutions 2 et 3, v2 a eu trois erreurs, autant que v1 à l’exécution 3. Les cinq mêmes billets ambigus ont changé de résultat au sein de chaque requête. Ce sont l’échelle, la piscine, la poêle, le code promotionnel et la lampe. T-64849 a reçu la bonne réponse une seule fois en six exécutions.

Le résumé honnête n’est donc pas « v2 est meilleure pour needs_human ». C’est plutôt : « v2 réduit peut-être un peu les fausses alertes; trois exécutions ne permettent pas de le savoir. » Sur 69 billets, une différence d’un ou deux billets, et même de trois, peut venir uniquement de l’échantillonnage. Pour établir un TermeIntervalle de confianceUne plage autour d’une valeur mesurée, obtenue par une méthode qui contient la vraie valeur 95 fois sur 100. Si l’intervalle d’une différence ne contient pas zéro, la différence n’est probablement pas due au hasard.En savoir plus Estimer l'incertitudeMathématiques et statistiques essentielles pour l'IA autour d’un tel résultat, consultez Quelle certitude peut-on avoir avec 24 billets?, dans le cours sur les modèles de langage. Pour comprendre pourquoi seuls les billets qui ont changé comptent, consultez Comparer des options dans le cours de mathématiques.

Pour détecter un petit effet, il faut plus de billets ou plus d’exécutions de chaque billet. Et mesurez ce que la modification devait accomplir. La requête v2 a ajouté une règle pour les réponses : « Si needs_human est vrai, dites qu’un membre de l’équipe contactera le client. » C’est une mesure où une requête peut faire une différence importante et claire. L’exercice du module ci-dessous porte sur cette mesure.

Exécutez l’ensemble fixe avant chaque mise en service d’une modification de requête. Conservez les résultats avec la version. Avec la simulation, une exécution est gratuite et prend une seconde. Avec un modèle réel, une exécution de 69 billets coûtait environ 0,02 $ US sur chat-small et 0,28 $ US sur chat-strong. Ce sont les prix au moment de l’enregistrement. Consultez la page des prix actuels du fournisseur.

Comparez aussi le coût de chaque version. v2 envoie environ 609 jetons de plus par billet. Pour 1 000 billets, chat-small est passé d’environ 0,27 $ US à 0,32 $ US. chat-strong est passé d’environ 3,67 $ US à 4,59 $ US. Le temps médian par billet est resté entre 2,5 et 3,1 secondes.

Un ensemble fixe a des limites. Il ne peut pas révéler des problèmes qu’il ne contient pas. Si vous ajustez une requête sur le même ensemble pendant des semaines, elle s’adapte à ces 69 billets, pas aux 69 suivants. Ajoutez de nouveaux billets réels à l’ensemble au fil du temps. Gardez-en aussi certains que vous ne regardez jamais pendant la rédaction des requêtes.

  1. Créez le fichier assistant/evaluate.py avec le code de la section ci-dessus.

  2. Créez le fichier tests/test_evaluate.py avec ce test. Il note trois lignes inventées dont vous pouvez compter les résultats à la main :

    tests/test_evaluate.py
    from assistant.data import Ticket
    from assistant.evaluate import Row, misses, score
    def test_score_counts_teams_needs_human_and_missing_answers():
    rows = [
    Row(Ticket("T-1", "C-1", "a", team="delivery", needs_human=False), {"team": "delivery", "needs_human": False}),
    Row(Ticket("T-2", "C-2", "b", team="payment", needs_human=True), {"team": "returns", "needs_human": True}),
    Row(Ticket("T-3", "C-3", "", team="", needs_human=True), None, "malformed_json"),
    ]
    s = score(rows)
    assert (s["team_correct"], s["team_scored"], s["needs_human_agree"], s["no_answer"]) == (1, 2, 2, 1)
    assert s["needs_human_true_caught"] == "1/2"
    assert len(misses(rows)) == 2
  3. Remplacez tout le contenu de assistant/__main__.py par cette version. Elle conserve analyse, ajoute eval et donne aux deux commandes les options --prompt et --model. La nouvelle fonction parse_answer lit le texte du modèle comme du JSON, ou indique pourquoi elle ne peut pas :

    assistant/__main__.py
    """Command line.
    python -m assistant analyse T-80008 --prompt v2 analyse one ticket with prompt v1 or v2
    python -m assistant eval --prompt v1 all 69 tickets of the evaluation set, scored
    """
    import argparse
    import json
    import sys
    from .config import Settings, make_provider
    from .context import build_request
    from .data import load_ticket, load_tickets
    from .evaluate import Row, misses, score
    from .providers import Completion, ProviderError
    def parse_answer(completion: Completion) -> tuple[dict | None, str]:
    """The model was asked for JSON. Return (the answer, "") or (None, why there is no answer)."""
    try:
    answer = json.loads(completion.text or "")
    except json.JSONDecodeError:
    return None, "not JSON"
    return (answer, "") if isinstance(answer, dict) else (None, "not a JSON object")
    def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(prog="python -m assistant")
    commands = parser.add_subparsers(dest="command", required=True)
    one = commands.add_parser("analyse", help="analyse one ticket")
    one.add_argument("ticket_id")
    many = commands.add_parser("eval", help="analyse every ticket of the evaluation set and score it")
    for command in (one, many):
    command.add_argument("--prompt", default="v2", choices=["v1", "v2"])
    command.add_argument("--model", help="overrides ASSISTANT_MODEL")
    args = parser.parse_args(argv)
    settings = Settings.from_env()
    model = args.model or settings.model
    provider = make_provider(settings)
    if args.command == "analyse":
    try:
    ticket = load_ticket(args.ticket_id)
    except KeyError as error:
    print(error.args[0], file=sys.stderr)
    return 2
    try:
    completion = provider.complete(build_request(ticket, model, args.prompt))
    except ProviderError as error:
    print(f"No answer: {error}", file=sys.stderr)
    return 1
    answer, problem = parse_answer(completion)
    print(json.dumps(answer, indent=2, ensure_ascii=False) if answer else f"{completion.text}\n({problem})")
    print(f"{completion.model} | prompt {args.prompt} | finish_reason: {completion.finish_reason} | "
    f"{completion.input_tokens} tokens in, {completion.output_tokens} out | {completion.latency_s} s")
    return 0
    rows = []
    for ticket in load_tickets().values():
    try:
    completion = provider.complete(build_request(ticket, model, args.prompt))
    except ProviderError as error:
    rows.append(Row(ticket, None, type(error).__name__))
    continue
    answer, problem = parse_answer(completion)
    rows.append(Row(ticket, answer, problem))
    print(json.dumps(score(rows), indent=2))
    print("\n".join(misses(rows)))
    return 0
    if __name__ == "__main__":
    sys.exit(main())
  4. Exécutez les tests. Vous devriez voir 21 passed, 1 deselected (21 tests réussis, 1 exclu).

  5. Exécutez le billet de démonstration. La dernière ligne devrait être gpt-6-luna-2026-09-22 | prompt v2 | finish_reason: stop | 1641 tokens in, 266 out | 5.094 s :

    Fenêtre de terminal
    python -m assistant analyse T-80008
  6. Exécutez python -m assistant eval --prompt v1, puis python -m assistant eval --prompt v2. Vérifiez que votre sortie correspond à celle de la section ci-dessus.

  7. Dans l’explorateur, sélectionnez chat-small et needs_human. Trouvez les quatre billets encadrés et lisez leurs raisons dans le tableau.

Votre projet correspond maintenant à l’instantané end-of-m02.

Avant toute exécution, faites une prédiction : sur chat-strong, v2 améliorera-t-elle needs_human autant que sur chat-small? Puis exécutez les deux versions :

Fenêtre de terminal
python -m assistant eval --prompt v1 --model chat-strong
python -m assistant eval --prompt v2 --model chat-strong
Afficher la réponse

Les deux versions donnent les mêmes scores : team_correct 68 sur 68, needs_human_agree 67 sur 69, needs_human_true_caught 9/9 et no_answer 0. Les deux mêmes billets sont incorrects dans les deux versions :

T-64849 needs_human True (expected False)
T-65177 needs_human True (expected False)

v2 n’a rien changé pour ces mesures. Le modèle plus puissant avait déjà deviné la règle de Grace avec v1. Le contrat plus clair n’avait donc rien à corriger. Les raisons ont changé : avec v2, le modèle nomme la règle qu’il applique. C’est H1 pour l’échelle instable et H3 pour la lampe. Cela rend ses réponses plus faciles à vérifier. Ces deux cas sont-ils vraiment des erreurs? Les deux demandent un jugement sur les règles écrites. Grace pourrait décider qu’une lampe n’est pas dans la liste de garantie (H3). La réponse attendue devrait alors changer. Une comparaison révèle souvent des règles peu claires, pas seulement des requêtes faibles.

Pour chat-strong, v2 coûte plus cher : environ 4,59 $ US contre 3,67 $ US par 1 000 billets. Elle n’apporte aucun gain pour ces mesures. Son gain concerne les réponses au client, que l’exercice du module mesure.

Erreur courante

Une version modifiée après sa mesure

Symptôme : Tomás aime l’idée de Grace pour les blessures et ajoute une phrase à v2.md : « Si le client est blessé, conseillez-lui de consulter un professionnel de la santé. » Puis il exécute eval --prompt v2, et tous les billets échouent :

{
"tickets": 69,
"no_answer": 69,
"team_correct": 0,
"team_scored": 68,
"needs_human_agree": 0,
"needs_human_true_caught": "0/9",
"notes": {
"RecordingNotFound": 69
}
}

Cause : la simulation répond uniquement aux requêtes exactes qui ont été enregistrées. Une requête modifiée est une requête différente. Avec un modèle réel, l’exécution fonctionnerait. Mais ses chiffres correspondraient à une nouvelle requête encore appelée v2. Le rapport « v2 : 68 sur 69 » décrirait alors une requête qui n’existe plus.

Correction :

  1. Annulez la modification dans v2.md. Exécutez de nouveau eval --prompt v2 et vérifiez que vous obtenez needs_human_agree 68.
  2. Écrivez la nouvelle idée dans un nouveau fichier, v3.md. Comparez-la à v2 sur les mêmes billets avec un modèle réel (TermeOllamaUn programme gratuit qui télécharge des modèles ouverts et les fait fonctionner sur votre ordinateur. Votre code l’appelle à une adresse locale, comme une API hébergée.En savoir plus Modèle hébergé ou autohébergéLes LLM : comment ils fonctionnent et comment en choisir un ou votre propre clé). Ajoutez v3 aux choices (choix) de --prompt.
  3. Ne modifiez jamais une version de requête après avoir communiqué ses résultats.

Le plan de ce module vous demande de comparer deux requêtes pour la classification et la rédaction de réponses sur le même échantillon. La commande eval mesure la classification. Mesurez maintenant les réponses aussi.

Les règles de réponse de Grace dans v2 donnent trois vérifications que le code peut faire :

  1. La réponse contient de 2 à 5 phrases.
  2. La réponse ne dit pas qu’une action a été faite (« a été approuvé », « a été envoyé », « j’ai traité »…).
  3. Si needs_human est vrai, la réponse dit qu’une personne contactera le client.

Écrivez un script compare_replies.py dans le dossier du projet. Il envoie les 69 billets de soutien avec v1, puis v2, à un seul modèle, simulé par défaut. Il applique les trois vérifications à chaque réponse et affiche les problèmes. Exécutez-le pour chat-small et pour chat-strong. Rédigez ensuite une courte note pour Grace, d’environ une demi-page. Dites ce qui s’est amélioré de v1 à v2 et ce qui échoue encore, avec des identifiants de billets et des citations. Dites quelles différences sont trop petites pour être fiables.

Le script est votre propre travail : il ne fait pas partie de l’instantané.

Afficher un exemple de réponse
compare_replies.py
"""Module practice: check the reply drafts of two prompt versions on the same 69 tickets.
python compare_replies.py # chat-small
python compare_replies.py chat-strong
"""
import re
import sys
from assistant.__main__ import parse_answer
from assistant.config import Settings, make_provider
from assistant.context import build_request
from assistant.data import load_tickets
from assistant.providers import ProviderError
DONE = re.compile(r"(?i)\b(has|have) been (approved|issued|processed|sent|refunded)\b|\bI'?ve (processed|issued|refunded)\b")
PERSON = re.compile(r"(?i)\b(contact|reach out|get in touch)\b")
def reply_problems(answer: dict) -> list[str]:
"""What is wrong with one reply draft, by Grace's three reply rules."""
reply = answer.get("reply") or ""
problems = []
sentences = len(re.findall(r"[.!?](?:\s|$)", reply))
if not 2 <= sentences <= 5:
problems.append(f"{sentences} sentences")
if DONE.search(reply):
problems.append("says that an action was done")
if answer.get("needs_human") is True and not PERSON.search(reply):
problems.append("does not say that a person will contact the customer")
return problems
def main() -> None:
model = sys.argv[1] if len(sys.argv) > 1 else "chat-small"
provider = make_provider(Settings.from_env())
for version in ("v1", "v2"):
found = []
for ticket in load_tickets().values():
try:
answer, _ = parse_answer(provider.complete(build_request(ticket, model, version)))
except ProviderError:
answer = None
if answer:
found += [f" {ticket.ticket_id} {p}" for p in reply_problems(answer)]
print(f"{model} {version}: {len(found)} reply problem(s)")
print("\n".join(found))
if __name__ == "__main__":
main()
Fenêtre de terminal
python compare_replies.py
python compare_replies.py chat-strong

La sortie, abrégée :

chat-small v1: 12 reply problem(s)
T-64704 does not say that a person will contact the customer
T-64849 does not say that a person will contact the customer
…
T-80007 does not say that a person will contact the customer
chat-small v2: 0 reply problem(s)
chat-strong v1: 10 reply problem(s)
T-64704 does not say that a person will contact the customer
T-64826 says that an action was done
…
T-80006 does not say that a person will contact the customer
chat-strong v2: 0 reply problem(s)

Lisez chaque résultat signalé avant de le compter. T-64826 est une fausse alerte de la vérification des expressions. La réponse dit : « pour que l’équipe des paiements vérifie si le remboursement a été envoyé ». C’est une question sur un remboursement, pas une affirmation. Les vérifications d’expressions offrent une protection, pas un jugement. Le module 3 aborde le même problème.

Une note pour Grace (exemple) :

« Nous avons comparé les requêtes v1 et v2 sur les mêmes 69 billets, avec les réponses enregistrées le 8 octobre 2026.

Ce qui s’est amélioré. Les réponses : avec v1, 12 réponses sur chat-small et 9 sur chat-strong définissaient needs_human à vrai. Mais elles ne disaient pas au client qu’une personne le contacterait. Par exemple, T-80004, la brûlure causée par le taille-haie, et T-80006, le billet vide. Avec v2 : aucune. (chat-strong v1 avait un autre résultat signalé, T-64826, une fausse alerte de la vérification des expressions.) C’est la règle que v2 a ajoutée, et la différence est beaucoup trop grande pour être due au hasard. Les raisons se sont aussi améliorées : avec v2, elles nomment la règle (H1 à H5), ce qui accélère leur vérification.

Ce qui n’a pas clairement changé. L’acheminement : 67 sur 68 sur chat-small et 68 sur 68 sur chat-strong avec les deux requêtes. Pour needs_human, chat-small est passé de 65 à 68 sur 69 dans l’enregistrement du cours. Mais quand nous avons envoyé les mêmes billets deux autres fois, v2 a obtenu 66 et 66, et v1, 65 et 66. Les mêmes cinq billets changent de résultat dans les deux versions. Cette différence est donc surtout du bruit.

Ce qui échoue encore. T-64849 (l’échelle en solde) obtient une réponse correcte dans une seule des six exécutions. Avec v2, il n’obtient parfois aucune réponse, car le modèle utilise tout son budget de jetons pour raisonner. Le module 3 traite ce problème. chat-strong signale T-64849 et T-65177 (la lampe) pour une intervention humaine avec les deux requêtes. Veuillez décider si une échelle instable pose un risque pour la sécurité (H1) et si une lampe est hors garantie (H3). Nous mettrons ensuite à jour les réponses attendues ou la politique. Enfin, les réponses de v2 concernant les blessures copient l’exemple 2 et ne recommandent plus de soins médicaux (T-80004). Une v3 pourrait ajouter ce conseil; nous la testerions sur les mêmes billets. »

Réinitialisez d’abord le projet au besoin. Si votre projet ne donne pas 21 passed, 1 deselected (21 tests réussis, 1 non sélectionné), copiez l’instantané end-of-m02. Suivez les étapes de Réinitialiser le projet, puis exécutez python -m pytest et python -m assistant analyse T-80008.

Vérifiez votre compréhension

Vérification des acquis : expliquez les améliorations observées de la requête v1 à v2 et les échecs qui restent. Donnez un billet comme exemple pour chacun et dites à quelles différences vous faites confiance.

Afficher la réponse

Améliorations : les réponses respectent la règle de Grace. Avec v1, 12 réponses sur chat-small définissaient needs_human à vrai sans dire qu’une personne contacterait le client (par exemple T-80006). Avec v2, aucune. Les raisons nomment la règle qui s’applique. Les fausses alertes pour needs_human ont diminué dans l’enregistrement du cours. T-64960, T-65047 et T-65083 ont été corrigés sur chat-small. Échecs restants : T-64849 (aucune réponse ou une mauvaise réponse dans cinq des six exécutions), T-65177 et T-64849 signalés par chat-strong avec les deux requêtes, et la copie de l’exemple 2 qui omet « consultez un professionnel de la santé » (T-80004). Confiance : la différence dans les réponses (12 contre 0) est grande et constante. La différence pour needs_human (65 contre 68) a diminué à environ un billet lors des exécutions répétées. Les mêmes cinq billets changent de résultat dans les deux versions, donc ce n’est pas une preuve. L’exactitude du choix de l’équipe n’a pas changé.

Vérifiez votre compréhension

Prédisez : vous écrivez la requête v3 et l'exécutez une fois sur les 69 billets. Elle obtient le bon résultat pour needs_human sur 69 billets, contre 68 pour v2. Larkfield devrait-elle passer à v3?

Afficher la réponse

Pas sur la base de ces résultats. Une différence d’un billet reste dans le bruit : v2 a elle-même varié entre 66 et 68 sur trois exécutions des mêmes requêtes. Regardez d’abord quel billet a changé et pourquoi. Exécutez ensuite les deux versions plusieurs fois, ou sur plus de billets, et comparez seulement les billets qui changent. Comparez aussi le coût par billet et les réponses. Passez à v3 si elle est meilleure sur les mesures importantes, ou équivalente et moins chère ou plus simple.

Vérifiez votre compréhension

Pourquoi une réponse manquante compte-t-elle comme incorrecte dans score(), au lieu d'être exclue?

Afficher la réponse

Une requête qui empêche le modèle de répondre est moins bonne pour ce billet, pas neutre. Si les réponses manquantes étaient exclues, une version pourrait sembler meilleure en échouant sur ses billets les plus difficiles. Avec v2, T-64849 n’a obtenu aucune réponse. L’exclure masquerait le fait que le billet sur l’échelle n’est toujours pas traité.

Récapitulatif

  • Comparez les requêtes comme des recettes. Utilisez des fichiers versionnés qui ne changent jamais et un ensemble fixe de billets, choisi avant de regarder les résultats. Utilisez les mêmes mesures, calculées par le code, le même modèle et la vue par billet.
  • Sur cet ensemble facile à acheminer, v1 et v2 donnent les mêmes résultats : 67 sur 68 sur chat-small, 68 sur 68 sur chat-strong. needs_human est passé de 65 à 68 sur chat-small dans un enregistrement. Mais les exécutions répétées montrent que c’est surtout du bruit. Le gain clair est dans les réponses : 12 problèmes avec v1, aucun avec v2.
  • Seuls les billets qui changent apportent de l’information. Sur 69 billets, une différence d’un à trois billets exige plus d’exécutions ou de billets avant d’être fiable.
  • Une seule réponse impressionnante ne prouve rien, et une seule exécution non plus.

Dans le module 3, la réponse n’est plus du texte libre que le code espère pouvoir analyser. Vous définissez un schéma, demandez une TermeSortie structuréeUne requête qui envoie au fournisseur le schéma de la réponse, pour que le modèle ne puisse écrire qu’une réponse de cette forme. Elle contrôle la forme, pas la vérité : l’application vérifie toujours chaque réponse.En savoir plus Définir un schémaCréer des applications fiables avec les API de LLM et vérifiez chaque réponse avant de la stocker.

Leçon suivante : Définir un schéma