0Pricing
AI Agents · Lekcja

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

  1. Gromadzenie i przechowywanie informacji zwrotnych
  2. Pętle refleksji i samokrytyki
  3. Samodoskonalenie na podstawie trajektorii
  4. Gdy samodoskonalenie idzie źle
← Powrót do AI Agents