0Pricing
AI Agents · Leçon

Collecte et stockage des retours

Recueillir les évaluations explicites et les signaux comportementaux implicites issus des interactions avec l’agent.

Collecte et stockage des retours est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.

Pourquoi les agents ont besoin de feedback

Un agent qui ne reçoit jamais de feedback est figé dans le temps : il ne peut pas progresser au-delà de son entraînement initial. Le feedback ferme la boucle entre ce que fait l’agent et ce que les utilisateurs veulent réellement.

Deux catégories sont particulièrement importantes : le feedback explicite (l’utilisateur évalue consciemment la sortie) et le feedback implicite (le comportement de l’utilisateur indique la qualité sans qu’il l’exprime).

Feedback explicite : pouces vers le haut ou vers le bas

La forme la plus simple : un signal binaire après chaque réponse. Facile à recueillir et à stocker, mais peu riche en informations.

Modèle d’implémentation : après la réponse de l’agent, proposez une invite de feedback et enregistrez le résultat avec l’ID du tour de conversation.

import uuid
from datetime import datetime

def collect_thumbs_feedback(turn_id: str, rating: str) -> dict:
    """rating: 'up' or 'down'"""
    assert rating in ('up', 'down'), 'Invalid rating'
    record = {
        'feedback_id': str(uuid.uuid4()),
        'turn_id': turn_id,
        'type': 'thumbs',
        'value': 1 if rating == 'up' else -1,
        'created_at': datetime.utcnow().isoformat()
    }
    return record

feedback = collect_thumbs_feedback('turn_abc123', 'up')
print(feedback)

Feedback explicite : évaluations par étoiles

Une évaluation de 1 à 5 étoiles offre davantage de granularité que les pouces. Elle permet de distinguer un résultat à peine acceptable (2 étoiles) d’un résultat excellent (5 étoiles), ce qui est utile pour affiner la qualité du signal.

Normalisez les valeurs entre 0 et 1 avant de les utiliser dans des chaînes de traitement d’entraînement.

def collect_star_feedback(turn_id: str, stars: int) -> dict:
    if not 1 <= stars <= 5:
        raise ValueError('Stars must be between 1 and 5')
    return {
        'turn_id': turn_id,
        'type': 'star',
        'raw_value': stars,
        'normalized': (stars - 1) / 4.0  # maps 1->0.0, 5->1.0
    }

fb = collect_star_feedback('turn_xyz456', 4)
print(fb)
# {'turn_id': 'turn_xyz456', 'type': 'star', 'raw_value': 4, 'normalized': 0.75}

Feedback explicite : corrections en texte libre

Le feedback en texte libre est le signal le plus riche. L’utilisateur écrit exactement ce qu’il attendait : « Le résumé était trop long », « Vous avez manqué l’idée principale », « Mauvaise devise — j’ai demandé EUR ».

Stockez les corrections en les associant à la sortie originale afin de pouvoir constituer ultérieurement des paires (sortie incorrecte → sortie corrigée) pour un ajustement fin supervisé.

def collect_correction_feedback(
    turn_id: str,
    original_output: str,
    corrected_output: str,
    user_note: str = ''
) -> dict:
    return {
        'turn_id': turn_id,
        'type': 'correction',
        'original': original_output,
        'corrected': corrected_output,
        'user_note': user_note
    }

fb = collect_correction_feedback(
    'turn_789',
    'The capital of Australia is Sydney.',
    'The capital of Australia is Canberra.',
    'Sydney is the largest city but not the capital.'
)
print(fb)

Feedback implicite : signal de nouvelle demande

Lorsqu’un utilisateur repose immédiatement la même question avec d’autres mots, il s’agit d’un signal implicite fort indiquant que la réponse précédente était incorrecte ou insuffisante. L’utilisateur n’a rien à cliquer : son comportement constitue lui-même le signal.

from datetime import datetime, timedelta

def detect_re_ask(
    current_msg: str,
    conversation_history: list,
    similarity_threshold: float = 0.7,
    window_seconds: int = 120
) -> bool:
    """
    Returns True if the current message is semantically similar
    to a recent message, suggesting dissatisfaction.
    """
    now = datetime.utcnow()
    for turn in conversation_history[-5:]:
        age = (now - turn['timestamp']).seconds
        if age <= window_seconds and turn['role'] == 'user':
            # In production: use embedding cosine similarity
            if simple_similarity(current_msg, turn['content']) >= similarity_threshold:
                return True
    return False

def simple_similarity(a: str, b: str) -> float:
    words_a = set(a.lower().split())
    words_b = set(b.lower().split())
    if not words_a or not words_b:
        return 0.0
    return len(words_a & words_b) / len(words_a | words_b)

if __name__ == '__main__':
    now = datetime.utcnow()
    history = [
        {'role': 'user', 'content': 'How do I reset my password', 'timestamp': now - timedelta(seconds=30)},
    ]
    result = detect_re_ask('How do I reset my password please', history)
    print('Re-ask detected:', result)

Feedback implicite : signal de modification de la sortie

Si l’agent génère du texte et que l’utilisateur le modifie avant de l’utiliser, la différence entre la version originale et la version modifiée constitue un feedback implicite. La version modifiée correspond à ce que l’utilisateur voulait réellement.

Ce cas est fréquent dans les assistants de rédaction, les générateurs de code et les outils de rédaction d’e-mails.

import difflib

def extract_edit_feedback(original: str, edited: str) -> dict:
    differ = difflib.unified_diff(
        original.splitlines(),
        edited.splitlines(),
        lineterm=''
    )
    diff_lines = list(differ)
    edit_ratio = difflib.SequenceMatcher(None, original, edited).ratio()
    return {
        'type': 'edit',
        'original': original,
        'edited': edited,
        'edit_distance': 1.0 - edit_ratio,  # 0=unchanged, 1=fully rewritten
        'diff': '\n'.join(diff_lines)
    }

fb = extract_edit_feedback(
    'Dear John, I am writing to inform you...',
    'Hi John, Just a quick note...'
)
print(f"Edit distance: {fb['edit_distance']:.2f}")

Schéma de stockage du feedback

Tous les types de feedback partagent un schéma commun avec des champs de données propres à chaque type. L’utilisation d’une table unique avec un discriminateur type et une colonne JSON payload simplifie les requêtes tout en prenant en charge tous les types de feedback.

# SQL schema for feedback storage

CREATE_TABLE_SQL = '''
CREATE TABLE agent_feedback (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    session_id  TEXT NOT NULL,
    turn_id     TEXT NOT NULL,
    agent_id    TEXT NOT NULL,
    type        TEXT NOT NULL CHECK (type IN ('thumbs','star','correction','re_ask','edit')),
    value       FLOAT,           -- numeric signal: +1/-1, 0-1, edit distance
    payload     JSONB,           -- type-specific data
    created_at  TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX ON agent_feedback (agent_id, created_at);
CREATE INDEX ON agent_feedback (type);
'''

# Example insert
INSERT_SQL = '''
INSERT INTO agent_feedback (session_id, turn_id, agent_id, type, value, payload)
VALUES ($1, $2, $3, $4, $5, $6)
'''

if __name__ == '__main__':
    print('Feedback table schema:')
    print(CREATE_TABLE_SQL)
    print('Insert statement:')
    print(INSERT_SQL)

Écriture d’une classe de collecte du feedback

Centraliser toute la collecte du feedback derrière une seule classe permet de garder le reste de la base de code propre. Le collecteur gère la déduplication, la mise en lots et les écritures asynchrones afin que le feedback ne bloque jamais la boucle principale de l’agent.

import asyncio
from collections import deque
from datetime import datetime

class FeedbackCollector:
    def __init__(self, agent_id: str, flush_interval: int = 30):
        self.agent_id = agent_id
        self.buffer: deque = deque(maxlen=1000)
        self.flush_interval = flush_interval

    def record(self, turn_id: str, fb_type: str, value: float, payload: dict):
        self.buffer.append({
            'turn_id': turn_id,
            'agent_id': self.agent_id,
            'type': fb_type,
            'value': value,
            'payload': payload,
            'created_at': datetime.utcnow().isoformat()
        })

    async def flush(self, db_client):
        while self.buffer:
            record = self.buffer.popleft()
            await db_client.insert('agent_feedback', record)

    async def start_auto_flush(self, db_client):
        while True:
            await asyncio.sleep(self.flush_interval)
            await self.flush(db_client)

if __name__ == '__main__':
    fc = FeedbackCollector(agent_id='agent-1')
    fc.record('turn-1', 'thumbs', 1.0, {'comment': 'Great answer'})
    fc.record('turn-2', 'thumbs', -1.0, {'comment': 'Wrong ticker'})
    print(f'Buffered {len(fc.buffer)} feedback records:')
    for rec in fc.buffer:
        print(' -', rec['type'], rec['value'], rec['payload'])

Agrégation du feedback pour l’analyse

Les enregistrements bruts de feedback doivent être agrégés avant d’être utiles pour prendre des décisions d’amélioration. Agrégations courantes : taux d’approbation par type d’intention, taux de correction au fil du time et catégories de sorties les plus modifiées.

from collections import defaultdict
from statistics import mean

def aggregate_feedback(records: list) -> dict:
    by_type = defaultdict(list)
    for r in records:
        by_type[r['type']].append(r['value'])

    summary = {}
    if 'thumbs' in by_type:
        values = by_type['thumbs']
        summary['approval_rate'] = (values.count(1) / len(values)) * 100
    if 'star' in by_type:
        summary['avg_star'] = mean(by_type['star']) * 4 + 1  # denormalize
    if 'edit' in by_type:
        summary['avg_edit_distance'] = mean(by_type['edit'])
    if 'correction' in by_type:
        summary['correction_count'] = len(by_type['correction'])
    return summary

records = [
    {'type': 'thumbs', 'value': 1},
    {'type': 'thumbs', 'value': -1},
    {'type': 'star', 'value': 0.75},
    {'type': 'edit', 'value': 0.3}
]
print(aggregate_feedback(records))

Confidentialité et consentement lors de la collecte du feedback

Le feedback contient souvent des données utilisateur sensibles. Bonnes pratiques : obtenez un consentement explicite avant d’enregistrer les corrections en texte libre, anonymise les identifiants de session avant l’analyse, fixez des limites de conservation (par exemple, supprimer les données après 90 jours) et ne journalisez jamais de PII dans les données de feedback.

import hashlib
import re

def anonymise_feedback(record: dict) -> dict:
    """Anonymise feedback record before storing for training."""
    safe = record.copy()

    # Hash the session_id so it can't be traced back to a user
    if 'session_id' in safe:
        safe['session_id'] = hashlib.sha256(
            safe['session_id'].encode()
        ).hexdigest()[:16]

    # Strip emails and phone numbers from correction text
    if 'payload' in safe and 'corrected' in safe['payload']:
        text = safe['payload']['corrected']
        text = re.sub(r'[\w.+-]+@[\w-]+\.[\w.]+', '[EMAIL]', text)
        text = re.sub(r'\+?[0-9][\s\-().]{7,}[0-9]', '[PHONE]', text)
        safe['payload'] = dict(safe['payload'], corrected=text)

    return safe

if __name__ == '__main__':
    record = {
        'session_id': 'sess-abc123',
        'payload': {'corrected': 'Contact me at jane@example.com or 555-123-4567'}
    }
    print('Anonymised record:', anonymise_feedback(record))

Chaîne de traitement du feedback de bout en bout

Pour tout réunir : collecter → anonymise → mise en mémoire tampon → flush → aggregate → créer un rapport. La chaîne de traitement s’exécute aux côtés de l’agent en production et produit un rapport hebdomadaire d’amélioration indiquant quelles intentions ont les taux d’approbation les plus faibles.

# Simplified end-to-end feedback pipeline sketch

class FeedbackPipeline:
    def __init__(self, agent_id: str):
        self.collector = FeedbackCollector(agent_id)
        self.records = []

    def on_thumbs(self, turn_id: str, rating: str):
        value = 1.0 if rating == 'up' else -1.0
        record = self.collector.record(turn_id, 'thumbs', value, {})
        self.records.append(record)

    def on_edit(self, turn_id: str, original: str, edited: str):
        fb = extract_edit_feedback(original, edited)
        record = self.collector.record(
            turn_id, 'edit', fb['edit_distance'], fb
        )
        self.records.append(record)

    def weekly_report(self) -> dict:
        return aggregate_feedback(
            [r for r in self.records]
        )

Vérification des connaissances

Quel signal de feedback ne nécessite aucune action consciente de la part de l’utilisateur ?

Récapitulatif : collecte et stockage du feedback

Excellent travail ! Voici ce que vous avez appris dans cette leçon :

  • Feedback explicite : pouces (binaire), étoiles (évalué), corrections (données d’entraînement appariées)
  • Feedback implicite : la détection des nouvelles demandes et la différence de modification des sorties indiquent la qualité sans effort de la part de l’utilisateur
  • Schéma de stockage : table unique avec discriminateur de type et charge utile JSONB
  • Chaîne de traitement : collecter → anonymise → mise en mémoire tampon → flush → aggregate
  • Confidentialité : hacher les identifiants de session, strip PII et fixer des limites de conservation

Dans la prochaine leçon, vous apprendrez comment les agents peuvent réfléchir à leurs propres performances et stocker ces réflexions comme mémoire épisodique.

Questions Fréquemment Posées

La leçon « Collecte et stockage des retours » est-elle gratuite ?

Oui — le texte complet de « Collecte et stockage des retours » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Collecte et stockage des retours » ?

Recueillir les évaluations explicites et les signaux comportementaux implicites issus des interactions avec l’agent. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Agents ?

Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.

Combien de temps prend la leçon « Collecte et stockage des retours » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?

Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Collecte et stockage des retours
  2. Boucles de réflexion et d’autocritique
  3. Auto-amélioration fondée sur les trajectoires
  4. Quand l’auto-amélioration tourne mal
← Retour à AI Agents