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
- Collecte et stockage des retours
- Boucles de réflexion et d’autocritique
- Auto-amélioration fondée sur les trajectoires
- Quand l’auto-amélioration tourne mal