Leçon 12 — Projet final

Projet (1/2) : cahier des charges et outils

6 min de lecture

Projet (1/2) : cahier des charges et outils

Le cahier des charges

Une boutique en ligne veut un assistant conversationnel (le même métier qu'un agent de commande sur WhatsApp comme Watpilot). Il doit :

  1. chercher un produit dans le catalogue par mot-clé (outil 1) ;
  2. consulter le stock d'une référence (outil 2) ;
  3. créer une commande (outil 3), uniquement après un récapitulatif et un « oui » explicite du client, avec au plus 50 unités et un stock suffisant ;
  4. transférer à un humain sur réclamation, demande hors périmètre ou demande explicite, et quand la boucle n'aboutit pas ;
  5. ne jamais inventer un prix, une référence ou un stock ;
  6. garder le contexte d'un message à l'autre (« oui » doit avoir un sens).
flowchart TB
  U["Client"] --> A["Assistant : boucle + historique"]
  A --> L["call_llm (module 2)"]
  A --> T1["chercher_produit"]
  A --> T2["consulter_stock"]
  A --> T3["creer_commande : confirmation, plafond, stock"]
  A --> H["transferer_a_un_humain"]
  A -->|"max_steps atteint"| H
  H --> Q["File d'attente des conseillers"]

Les étapes

  1. Reprenez agent.py du module 2 (call_llm et les deux premiers outils), et ajoutez au catalogue "P3": ("Chocolat noir 100 g", 3.2) avec un stock de 40.
  2. Écrivez creer_commande et transferer_a_un_humain, puis les 4 schémas et le prompt système (cette partie).
  3. Assemblez la classe Assistant, testez et évaluez (partie 2).
Règle métierOù la mettrePourquoi
Pas de commande sans confirmationparamètre confirme + description de l'outiloblige le modèle à récapituler d'abord
Au plus 50 unitésdans le code de creer_commandeun prompt peut être contourné
Stock suffisantdans le code de creer_commandedonnée exacte, pas une estimation du modèle
Commande au nom du bon clientclient_id injecté par le codele modèle ne doit pas pouvoir le choisir

Le modèle doit-il fournir le client_id en argument de creer_commande ?

Le paramètre confirme=true de creer_commande est rempli par le modèle. Quelles affirmations sont justes ? (plusieurs réponses)

Exercice : les outils, les schémas et le prompt

Dans assistant_commandes.py (qui fait from agent import call_llm, ainsi que vos CATALOGUE, STOCK, chercher_produit et consulter_stock), écrivez :

  • creer_commande(client_id, ref, quantite, confirme) qui applique les règles du tableau, décrémente le stock, enregistre la commande dans un dictionnaire COMMANDES et renvoie {"numero", "total"} ;
  • transferer_a_un_humain(client_id, motif) qui ajoute une entrée à une liste FILE_HUMAINE ;
  • la liste TOOLS des 4 schémas (sans client_id) et le prompt SYSTEM.

Chaque règle violée doit lever une exception au message clair : run_tool la renverra au modèle.

Voir la solution
assistant_commandes.py (partie 1)
import json
import uuid

COMMANDES = {}           # simplification : une vraie base en production
FILE_HUMAINE = []        # simplification : un outil de ticketing en production
QUANTITE_MAX = 50


def creer_commande(client_id: str, ref: str, quantite: int, confirme: bool) -> dict:
    if not confirme:
        raise ValueError("Récapitule (produit, quantité, total) et demande "
                         "confirmation au client avant de créer la commande.")
    if ref not in CATALOGUE:
        raise ValueError(f"Référence inconnue : {ref}")
    if not 1 <= quantite <= QUANTITE_MAX:
        raise ValueError(f"Quantité hors limites (1 à {QUANTITE_MAX}) : "
                         "propose le transfert à un conseiller.")
    if STOCK[ref] < quantite:
        raise ValueError(f"Stock insuffisant : {STOCK[ref]} disponible(s).")
    STOCK[ref] -= quantite
    numero = "CMD-" + uuid.uuid4().hex[:8].upper()
    total = round(CATALOGUE[ref][1] * quantite, 2)
    COMMANDES[numero] = {"client": client_id, "ref": ref,
                         "quantite": quantite, "total": total}
    return {"numero": numero, "total": total}


def transferer_a_un_humain(client_id: str, motif: str) -> dict:
    FILE_HUMAINE.append({"client": client_id, "motif": motif})
    return {"transfere": True, "message": "Un conseiller reprend la conversation."}


def schema(name, description, **props):
    return {"type": "function", "function": {
        "name": name, "description": description,
        "parameters": {"type": "object", "properties": props,
                       "required": list(props)}}}


TOOLS = [
    schema("chercher_produit", "Cherche des produits du catalogue par mot-clé. "
           "Renvoie référence, nom et prix.", texte={"type": "string"}),
    schema("consulter_stock", "Quantité en stock d'une référence (ex. P1). "
           "Appeler après chercher_produit.", ref={"type": "string"}),
    schema("creer_commande", "Crée la commande. Mettre confirme=true UNIQUEMENT si le "
           "client vient de dire explicitement oui au récapitulatif.",
           ref={"type": "string"}, quantite={"type": "integer", "minimum": 1},
           confirme={"type": "boolean"}),
    schema("transferer_a_un_humain", "Passe la main à un conseiller : réclamation, "
           "demande hors catalogue, client mécontent, ou demande explicite.",
           motif={"type": "string"}),
]

SYSTEM = (
    "Tu es l'assistant de commandes d'une boutique en ligne. Tu aides à trouver un "
    "produit, vérifier le stock et passer commande. Règles : n'invente jamais un prix, "
    "une référence ni un stock, utilise les outils ; avant de créer une commande, "
    "récapitule produit, quantité et total et attends un oui explicite ; hors de ce "
    "périmètre, ou si le client le demande, transfère à un humain. Réponds en "
    "français, en phrases courtes."
)

ESCALADE = "Je vous mets en relation avec un conseiller."

Le petit utilitaire schema() évite de répéter la structure JSON quatre fois ; tous les paramètres y sont obligatoires, ce qui convient ici.

Je sais faire

  • Traduire un cahier des charges en outils, paramètres et règles
  • Décider pour chaque règle si elle va dans le prompt, le schéma ou le code
  • Écrire des outils qui lèvent des erreurs lisibles par le modèle
  • Cacher au modèle les paramètres qu'il ne doit pas contrôler (client_id)
Suivant