Podręcznik uczestnika z objaśnieniami, przykładami i ćwiczeniami do samodzielnej nauki do Modułu 6

4. R3. Ranking i metryki wyszukiwania

4.1. Zapis pełnego rankingu i wykonanie R3

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

Wynik jest użytecznym dowodem, gdy zachowuje dane pozwalające odtworzyć ocenę. Lista samych nazw źródeł nie pokazuje kolejności, score ani fragmentów odrzuconych przez filtr. Zapisuj więc ranking przed progiem i kontekst po jego zastosowaniu.

Cztery identyfikatory

test_id identyfikuje pytanie lub scenariusz, turn_id turę, run_id wykonanie serii, a result_id konkretny wynik. Dwa wykonania tego samego testu mają wspólny test_id, lecz inne result_id. contract_hash identyfikuje treść kontraktu danej tury, dzięki czemu zmiana oczekiwań nie może zostać niezauważona podczas łączenia ocen.

Wiersz retrieval_runs.jsonl zawiera pełny tekst i metadane wyników. Zajmuje więcej miejsca, ale nie wymaga zgadywania, jaki fragment miał model. deepcopy chroni zapis w pamięci przed późniejszą modyfikacją listy rankingu. Nie chroni pliku przed ręczną edycją, dlatego surowe wyniki zachowuj bez zmian.

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

def build_retrieval_record(test_case, ranking, context, config, run_id,
                           *, origin='isolated_retrieval', question=None):
    expected = {
        cid for g in test_case['expected_sources']
        for cid in g['required_chunk_ids'] + g.get('alternative_chunk_ids', [])
    }
    rank = next((i for i, r in enumerate(ranking, 1)
                 if r['chunk_id'] in expected), None)
    return {
        'schema_version': 'm6.1', 'result_id': uuid4().hex,
        'run_id': run_id, 'created_at': datetime.now(timezone.utc).isoformat(),
        'test_id': test_case['test_id'], 'turn_id': test_case['turn_id'],
        'test_set_version': test_case['test_set_version'],
        'contract_hash': fingerprint(test_case),
        'origin': origin, 'mode': config['mode'], 'config': deepcopy(config),
        'search_question': question or prepare_search_question(test_case),
        'ranking': deepcopy(ranking), 'context': deepcopy(context),
        'metrics': source_metrics(test_case, ranking, context),
        'hit_at_1': hit_at_k(test_case, ranking, 1, config['top_k']),
        'hit_at_k': hit_at_k(test_case, ranking, config['top_k'], config['top_k']),
        'metric_k': config['top_k'], 'expected_rank': rank,
        'metric_reason': None if expected else 'no_expected_chunk',
        'status': 'completed',
    }

Funkcja zachowuje schemat pakietu i dopisuje hit_at_1, hit_at_k, metric_k, expected_rank oraz przyczynę nieoceniania metryki. Nazwy metrics.ranking.any_group i all_groups odpowiadają znaczeniu retrieval_any_hit i retrieval_all_hit na slajdach. To jawne mapowanie schematu, nie dwie różne miary.

Próg i filtr struktury

Filtr score odrzuca wyniki poniżej progu. Wartość None wyłącza ten filtr, ale nie wyłącza sprawdzania pustego tekstu, metadanych i powtórzonych fragmentów. prepare_context_results nadal wybiera użyteczne strukturalnie rekordy. Oddziel brak wyników (no_results), odrzucenie wszystkich przez próg (below_threshold) i brak rekordów użytecznych strukturalnie (no_usable_results).

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

def evaluate_retrieval_turn(test_case, config, run_id):
    check_config(config)
    effective = dict(config, **test_case.get('config_overrides', {}))
    question = prepare_search_question(test_case)
    if question is None:
        return None
    try:
        ranking = rag_backend.retrieve(question, top_k=effective['top_k'])
        context, reason = rag_backend.select_context_results(
            ranking, effective['similarity_threshold'])
        record = build_retrieval_record(test_case, ranking, context, effective, run_id)
        record['context_reason'] = reason
        if test_case['test_id'] == 'M6-T106' and any(r['score'] >= 1 for r in ranking):
            record.update(status='precondition_failed', error='T106 wymaga score < 1.')
        return record
    except Exception as exc:
        record = build_retrieval_record(test_case, [], [], effective, run_id)
        record.update(status='execution_error', error=str(exc), metrics=None,
                      hit_at_1=None, hit_at_k=None, expected_rank=None,
                      metric_reason='execution_error')
        return record

Wyjątek techniczny tworzy rekord execution_error, a metryki stają się nieoceniane. Gdyby pozostawić puste listy i zwykłe zero Hit@k, uszkodzony checkpoint wyglądałby jak poprawnie wykonane, lecz nietrafne wyszukiwanie. Zapis precondition_failed w T106 sygnalizuje inną sytuację: nie zaszło założenie wymagane do tej kontrolowanej próby.

Uruchomienie serii i odczyt wyniku

Gotowa pętla run_retrieval z dodatku nadaje jeden run_id serii, wywołuje Twoje evaluate_retrieval_turn i natychmiast dopisuje rekordy. Tury z retrieval_question=None pomija. Poniższy kod uruchamia sprawdzony podzbiór offline.

Notebook warsztatu · przykład wywołania

ids = (ref.read_json(ROOT / 'dane/przypadki/checkpoint_subset.json')
       if MODE == 'checkpoint' else None)
retrieval_results = w.run_retrieval(cases, config, E, test_ids=ids)
for row in retrieval_results:
    print(row['test_id'], row['turn_id'], row['status'],
          row['hit_at_1'], row['hit_at_k'], row['metrics'])
ref.save_json(E / 'R3_selected_result_ids.json',
              [r['result_id'] for r in retrieval_results])

Nie uruchamiaj całego zestawu z założeniem, że każdy tekst ma checkpoint. Gdy tworzysz własne pytania, wybierz świadomie Ollama i rozpocznij osobną serię. Zachowaj listę planowanych przypadków, wykonanych rekordów, pominiętych tur oraz błędów technicznych.

Rzeczywisty przykład T004

Pytanie o nieudane uruchomienie wymaga fragmentu DOC-INSTR-dd6055d675-0003. W dostarczonym pilocie R3 przy top_k=3 pojawiają się fragment restartu, testu po zmianie oraz kontroli znaków. Brakuje procedury przywrócenia kopii. Mimo podobieństwa tematycznego pokrycie wymaganej grupy wynosi 0. Przy top_k=5 grupa pojawia się w zapisanym rankingu i kontekście.

Ten wynik pochodzi z odtworzenia danych dostarczonego indeksu. Nie przenoś go na T101 tylko dlatego, że oba pytania wspominają niepowodzenie. Nie dowodzi również, że wygenerowana odpowiedź po zmianie top_k będzie kompletna. To osobna próba R4.

Kontrola R3: potrafisz otworzyć jeden wiersz JSONL i wskazać konkretne wejście, ranking, filtr, kontekst, konfigurację i kontrakt. Liczby wynikają z tych danych, a nie z opisu słownego eksperymentu.

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