6. R5. Od objawu do potwierdzonej przyczyny

6.2. Powtarzanie prób i uczciwe podsumowanie

ENHANCER+ · Moduł 6 · Ewaluacja RAG · Podrozdział

Retrieval dla stałego pytania, wektora, indeksu i deterministycznego algorytmu może być powtarzalny. Generowanie odpowiedzi i wybór narzędzia zależą od modelu. Niska temperatura i seed pomagają kontrolować warunki, ale nie dają ogólnej gwarancji identycznego wyniku na każdym środowisku.

Trzy nowe wykonania

W metodzie tego kursu ocenę stabilności testu zależnego od modelu opierasz na trzech nowych niezależnych wykonaniach. Każde ma nowy run_id i nową sesję dla niezależnego przypadku. Wewnątrz scenariusza zachowujesz ciąg rzeczywistych tur. Pojedyncza demonstracja pozostaje dopuszczalna, gdy nie wyciągasz z niej wniosku o stabilności.

Notebook warsztatu · przykład wywołania

RUN_STABILITY = False
if RUN_STABILITY:
    assert MODE == 'ollama', 'Checkpoint nie mierzy zmienności modelu.'
    repeated_results = []
    for repetition in range(3):
        repeated_results.extend(
            w.run_assistant(cases, config, E, test_ids=['M6-T101']))
    ref.save_json(E / 'stability_result_ids.json',
                  [r['result_id'] for r in repeated_results])
    drafts = [ref.review_template(
        r, contracts[(r['test_id'], r['turn_id'])], 'answer')
        for r in repeated_results]
    ref.save_json(E / 'stability_reviews_draft.json', drafts)
    # Teraz przeczytaj trzy odpowiedzi i uzupełnij plik ocen.
    # Nie uruchamiaj tej komórki ponownie, aby zapisać oceny.

Nie kopiuj jednego wyniku pod trzema identyfikatorami. Nie traktuj trzech odczytów checkpointu jako trzech nowych odpowiedzi. Nie wybieraj najlepszego tekstu spośród prób, pomijając pozostałe. Jeżeli jeden przebieg kończy się błędem środowiska, zgłoś go i uzupełnij serię zgodnie z planem; nie nadaj brakującej treści oceny zero.

Minimum i mediana osobno dla każdego kryterium

Dla ocen poprawności 2, 1, 2 minimum wynosi 1, a mediana 2. Minimum ujawnia słabszą próbę, a mediana typowy środkowy wynik. Nie mieszaj w jednej liście poprawności, szumu kontekstu i precyzji odpowiedzi. Kryteria mierzą inne rzeczy.

Policz liczbę błędów krytycznych, sprawdź zgodność statusów i zestawów źródeł. Stabilne statusy nie dowodzą stabilnej jakości, bo trzy różne teksty mogą mieć status answered. Stabilne źródła także nie gwarantują, że model właściwie je wykorzystał. Potrzebne są trzy osobne recenzje.

R5 · Do pliku kod/moj_m6.py · zastąp całą definicję

def summarize_repeats(results, reviews, *, allow_replay=False):
    if not results:
        raise ValueError('Brak wyników.')
    review_map = {r['result_id']: r for r in reviews}
    if len(review_map) != len(reviews):
        raise ValueError('Powtórzona recenzja.')
    groups = {}
    for row in results:
        if row.get('execution_status') != 'completed':
            raise ValueError('Próba niewykonana poprawnie; raportuj ją osobno.')
        if not allow_replay and (row['mode'] != 'ollama' or row['origin'] != 'assistant_run'):
            raise ValueError('Stabilność modelu wymaga nowych wykonań Ollama.')
        if row['result_id'] not in review_map:
            raise ValueError('Brakuje oceny któregoś wykonania.')
        rev = review_map[row['result_id']]
        if rev['contract_hash'] != row['contract_hash'] or rev['review_kind'] != 'answer':
            raise ValueError('Niezgodny kontrakt lub rodzaj recenzji.')
        groups.setdefault((row['test_id'], row['turn_id']), []).append((row, rev))
    summary = []
    for key, pairs in groups.items():
        signatures = {(r['contract_hash'], fingerprint(r['config'])) for r, _ in pairs}
        if len(pairs) != 3 or len({r['run_id'] for r, _ in pairs}) != 3 or len(signatures) != 1:
            raise ValueError('Potrzebne są trzy różne run_id przy tym samym kontrakcie i konfiguracji.')
        scores = {}
        for criterion in ANSWER:
            values = [v['scores'][criterion] for _, v in pairs]
            if any(v != 'ND' and (type(v) is not int or v not in (0, 1, 2)) for v in values):
                raise ValueError('Nieukończona ocena.')
            numeric = [v for v in values if type(v) is int]
            scores[criterion] = {'n': len(numeric),
                'minimum': min(numeric) if numeric else None,
                'median': statistics.median(numeric) if numeric else None}
        statuses = [r['raw_result']['status'] for r, _ in pairs]
        sources = [sorted(s['chunk_id'] for s in r['raw_result'].get('sources', [])) for r, _ in pairs]
        summary.append({'test_id': key[0], 'turn_id': key[1], 'scores': scores,
            'critical_errors': sum(v['critical_error'] is True for _, v in pairs),
            'pass_all': all(v['verdict'] == 'pass' for _, v in pairs),
            'statuses_stable': len(set(statuses)) == 1,
            'sources_stable': all(s == sources[0] for s in sources),
            'new_measurements': all(r['mode'] == 'ollama' and r['origin'] == 'assistant_run' for r, _ in pairs)})
    return summary

Funkcja domyślnie wymaga nowych wyników Ollama. Parametr allow_replay=True służy wyłącznie do sprawdzania obliczeń na kontrolowanych danych testowych. Taki wynik nie staje się badaniem zmienności modelu. Pole new_measurements zachowuje tę różnicę.

Trzy próby są niewielką obserwacją przyjętą na potrzeby kursu. Nie stanowią oszacowania niezawodności dla wszystkich użytkowników ani gwarancji bezpieczeństwa wdrożenia. W przypadkach krytycznych plan wymaga spełnienia warunku w każdej próbie; podaj ten fakt w raporcie bez rozszerzania wniosku poza badaną próbę.

Zapis trzech ocen i podsumowania

Komórka wykonująca próby tworzy stability_reviews_draft.json. Uzupełnij w nim wszystkie trzy recenzje, korzystając z ich result_id i zapisanych odpowiedzi. Następna komórka odczytuje wyniki z pliku, więc nie wymaga ponownego generowania. Włącz zapis dopiero po ocenie. Walidator zatrzyma niekompletne recenzje. Po udanym zapisie pozostaw przełącznik wyłączony, aby nie dopisywać tych samych ocen ponownie.

Notebook warsztatu · przykład wywołania

# Po uzupełnieniu pliku stability_reviews_draft.json.
SAVE_STABILITY_REVIEWS = False
if SAVE_STABILITY_REVIEWS:
    result_ids = set(ref.read_json(E / 'stability_result_ids.json'))
    selected_results = [r for r in ref.read_jsonl(E / 'assistant_results.jsonl')
                        if r['result_id'] in result_ids]
    assert len(selected_results) == len(result_ids) == 3
    by_id = {r['result_id']: r for r in selected_results}
    selected_reviews = ref.read_json(E / 'stability_reviews_draft.json')
    assert {v['result_id'] for v in selected_reviews} == result_ids
    for review in selected_reviews:
        r = by_id[review['result_id']]
        w.save_review(E / 'assistant_reviews.jsonl', review, r,
                      contracts[(r['test_id'], r['turn_id'])])
    summary = w.summarize_repeats(selected_results, selected_reviews)
    ref.save_json(E / 'stability_summary.json', summary)
    print(summary)

Mianowniki i brakujące dane

Rozdziel liczbę kontraktów, liczbę tur, liczbę wykonań i liczbę recenzji. Pełny zestaw ma 24 przypadki i 26 tur. Trzy wykonania wszystkich tur dają 78 wyników, ale nadal dotyczą 24 przypadków. Dodatkowy import M5 nie zwiększa liczby nowych testów M6.

Odsetek zaliczonych wyników musi mieć jawny mianownik. Możesz podać odsetek wśród ocenionych tur oraz pokrycie oceną całego planowanego zestawu. Jeżeli oceniono tylko najłatwiejsze przypadki, wysoki odsetek w tej grupie nie opisuje całego systemu. N/D wyłączasz z konkretnej metryki, a brak recenzji pokazujesz oddzielnie.

Czas i koszt

Do czasu całej tury używaj pomiaru obejmującego resolver, selektor, narzędzie, retrieval i generację w tej samej konfiguracji. W diagnostyce M5 dostępny jest duration_ms. Nie mieszaj tego czasu z czasem samego wywołania LLM ani z odczytem checkpointu. Jeśli porównujesz czasy nowych wykonań, opisz sprzęt, tryb, obciążenie, rozgrzanie modelu i zakres pomiaru.

Zestaw rozwojowy i sprawdzający

Jeżeli wielokrotnie dopasowujesz prompt do tych samych pytań, poprawa może dotyczyć głównie znanych przykładów. Zachowaj dodatkowe pytania sprawdzające, których nie używasz do wyboru wariantu. Rozdzielaj je według intencji i scenariuszy: prawie identyczna parafraza w obu częściach daje słabszą kontrolę uogólnienia. W małym pakiecie traktuj wyniki jako diagnozę i porównanie prototypu, nie ranking uniwersalnej jakości modelu.

Sprawdź się: potrafisz wskazać wszystkie trzy wyniki i recenzje, a wniosek nie ukrywa minimum, braków ani pojedynczego krytycznego niepowodzenia.

Lokalne chatboty, agenci i systemy RAG z własną bazą wiedzy · Moduł 6 · Wydanie 29.09.2026