Gromadzenie i przechowywanie informacji zwrotnych
Rejestrowanie jawnych ocen i niejawnych sygnałów behawioralnych z interakcji z agentem.
Gromadzenie i przechowywanie informacji zwrotnych to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.
Dlaczego agenci potrzebują informacji zwrotnych
Agent, który nigdy nie otrzymuje informacji zwrotnych, pozostaje niezmienny — nie może rozwijać się poza swoim początkowym treningiem. Informacje zwrotne zamykają pętlę między działaniami agenta a tym, czego rzeczywiście oczekują użytkownicy.
Najważniejsze są dwie kategorie: bezpośrednie informacje zwrotne (użytkownik świadomie ocenia wynik) oraz pośrednie informacje zwrotne (zachowanie użytkownika sygnalizuje jakość, choć użytkownik tego nie mówi).
Bezpośrednie informacje zwrotne: kciuk w górę/w dół
Najprostsza forma: sygnał binarny po każdej odpowiedzi. Łatwy do zebrania i zapisania, ale zawierający niewiele informacji.
Schemat implementacji: po odpowiedzi agenta wyświetl prośbę o przekazanie informacji zwrotnej i zapisz wynik razem z identyfikatorem tury rozmowy.
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)Bezpośrednie informacje zwrotne: oceny w gwiazdkach
Ocena w skali 1–5 gwiazdek zapewnia większą szczegółowość niż kciuki. Pozwala odróżnić wynik zaledwie akceptowalny (2 gwiazdki) od doskonałego (5 gwiazdek), co jest przydatne do poprawy jakości sygnału używanego podczas dostrajania.
Przed użyciem w potokach treningowych znormalizuj wartości do zakresu 0–1.
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}Bezpośrednie informacje zwrotne: korekty w wolnym tekście
Informacje zwrotne w wolnym tekście są najbogatszym sygnałem. Użytkownik dokładnie opisuje, czego oczekiwał: 'Podsumowanie było zbyt długie', 'Pominięto główną myśl', 'Nieprawidłowa waluta — chodziło o EUR'.
Zapisuj korekty powiązane z pierwotnym wynikiem, aby później móc sparować (błędny wynik → poprawiony wynik) na potrzeby dostrajania nadzorowanego.
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)Pośrednie informacje zwrotne: sygnał ponownego zadania pytania
Gdy użytkownik natychmiast zadaje ponownie to samo pytanie innymi słowami, jest to silny pośredni sygnał, że poprzednia odpowiedź była błędna lub niewystarczająca. Użytkownik nie musi niczego klikać — sam sposób jego działania jest sygnałem.
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)
Pośrednie informacje zwrotne: sygnał edycji wyniku
Jeśli agent wygeneruje tekst, a użytkownik edytuje go przed użyciem, różnica między oryginałem a edycją jest pośrednią informacją zwrotną. Wersja po edycji odzwierciedla to, czego użytkownik rzeczywiście potrzebował.
Jest to częste w asystentach do pisania, generatorach kodu i narzędziach do tworzenia wiadomości e-mail.
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}")Schemat przechowywania informacji zwrotnych
Wszystkie typy informacji zwrotnych korzystają ze wspólnego schematu z polami ładunku zależnymi od typu. Użycie jednej tabeli z wyróżnikiem type i kolumną JSON payload upraszcza zapytania, a jednocześnie obsługuje dowolny typ informacji zwrotnych.
# 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)
Pisanie klasy zbierającej informacje zwrotne
Centralizacja zbierania wszystkich informacji zwrotnych w jednej klasie pozwala zachować porządek w pozostałej części bazy kodu. Klasa zbierająca obsługuje usuwanie duplikatów, grupowanie i asynchroniczne zapisy, dzięki czemu informacje zwrotne nigdy nie blokują głównej pętli agenta.
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'])
Agregowanie informacji zwrotnych na potrzeby analizy
Surowe rekordy informacji zwrotnych wymagają agregacji, zanim będą przydatne do podejmowania decyzji o ulepszeniach. Typowe agregacje to: wskaźnik akceptacji dla każdego typu intencji, odsetek korekt w czasie oraz kategorie wyników najczęściej edytowanych.
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))Prywatność i zgoda podczas zbierania informacji zwrotnych
Informacje zwrotne często zawierają poufne dane użytkowników. Dobre praktyki obejmują: uzyskanie wyraźnej zgody przed rejestrowaniem korekt w wolnym tekście, anonimizację identyfikatorów sesji przed analizą, ustalenie limitów przechowywania (np. usuwanie po 90 dniach) oraz rezygnację z rejestrowania PII w ładunkach informacji zwrotnych.
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))
Kompletny potok informacji zwrotnych
Połączenie wszystkich elementów wygląda następująco: zbieraj → anonimizuj → buforuj → opróżniaj bufor → agreguj → raportuj. Potok działa w środowisku produkcyjnym równolegle z agentem i generuje cotygodniowy raport ulepszeń, pokazujący, które intencje mają najniższe wskaźniki akceptacji.
# 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]
)Sprawdzenie wiedzy
Który sygnał informacji zwrotnej nie wymaga od użytkownika żadnego świadomego działania?
Podsumowanie: zbieranie i przechowywanie informacji zwrotnych
Świetna praca! Oto najważniejsze informacje z tej lekcji:
- Bezpośrednie informacje zwrotne: kciuki (sygnał binarny), gwiazdki (ocena stopniowana), korekty (sparowane dane treningowe)
- Pośrednie informacje zwrotne: wykrywanie ponownego zadania pytania i odległość edycji wyniku sygnalizują jakość bez wysiłku użytkownika
- Schemat przechowywania: pojedyncza tabela z wyróżnikiem typu i ładunkiem JSONB
- Potok: zbieraj → anonimizuj → buforuj → opróżniaj bufor → agreguj
- Prywatność: haszuj identyfikatory sesji, usuwaj PII, ustalaj limity retencji
W następnej lekcji dowiedzą się Państwo, jak agenci mogą reflektować nad własnym działaniem i zapisywać te refleksje jako pamięć epizodyczną.
Często zadawane pytania
Czy lekcja „Gromadzenie i przechowywanie informacji zwrotnych” jest bezpłatna?
Tak — pełny tekst „Gromadzenie i przechowywanie informacji zwrotnych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.
Co nauczysz się w „Gromadzenie i przechowywanie informacji zwrotnych”?
Rejestrowanie jawnych ocen i niejawnych sygnałów behawioralnych z interakcji z agentem. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Agents?
Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Gromadzenie i przechowywanie informacji zwrotnych”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?
Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Gromadzenie i przechowywanie informacji zwrotnych
- Pętle refleksji i samokrytyki
- Samodoskonalenie na podstawie trajektorii
- Gdy samodoskonalenie idzie źle