Strona główna / Artykuły / Uwagi praktyczne: RAG do tarczy do rzucania strzałkami: gdy Top-K zwraca trzy wersje tego samego

Uwagi praktyczne: RAG do tarczy do rzucania strzałkami: gdy Top-K zwraca trzy wersje tego samego

Krok po kroku instrukcja obsługi Notatek praktycznych: Dartboard RAG – gdy Top-K zwraca trzy wersje tego samego treści: kontrakty, sprawdzenia oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.

2352 słów

Poniższe notatki przedstawiają praktyczne podejście do problemu „Dartboard RAG: Gdy Top-K zwraca trzy wersje tego samego fragmentu tekstu”. Nacisk kładziony jest na umowy, sprawdzenia oraz miejsca zastępcze dla kodu, a nie na motywacyjne aspekty.

Problem duplikatów kontekstu

Gdy pracujesz nad etapem rozwiązywania problemu duplikatów kontekstu, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Obok wyników funkcjonalnych zapisz czas wykonywania oraz koszt tokenów lub zapytań. Jasna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się od środowiska demonstracyjnego do wspólnych środowisk. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów – częste zmiany promptów rzadko poprawiają słabą efektywność wyszukiwania.

Top 3:
1. "Greenhouse gases trap heat in the atmosphere, causing warming..."  (sim 0.91)
2. "Atmospheric greenhouse gases are the primary driver of climate change..."  (sim 0.89)
3. "The trapping of heat by greenhouse gases leads to rising temperatures..."  (sim 0.88)

Analogia z tarczą do rzucania

Gdy przechodzisz przez etap analogii do tarczy do strzelania, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Trzymaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury. Zmierz stopę odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania.

Chunks plotted by relevance to query
                    (closer to center = higher cosine sim)

                       ●●●         ← cluster of near-duplicate chunks
                      ●●●          (all about "greenhouse gases")
                      ● bull's-eye = QUERY

                                ●     ← chunk about deforestation
                                       (relevant but different topic)

                    ●           ●     ← chunks about agriculture, ocean carbon

                       ●  ●          ← chunks about historical climate


   STANDARD TOP-3 picks:           DARTBOARD TOP-3 picks:
   3 closest darts                 1 closest, then darts that are also
   = 3 darts in the same           good but spread across the board
     spot near bull's-eye          = better coverage of relevant content

Proces przetwarzania

Gdy przechodzisz przez etap pipeline, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

Krok 1 — Nadmiarowe pobieranie danych za pomocą FAISS

Gdy przechodzisz przez krok 1 „Over-fetch with stage”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolę małe, testowalne jednostki niż rozbudowane skrypty. Gdy jakiś krok się nie udaje, błąd powinien wskazywać na jedną konkretne odpowiedzialność, a nie na skomplikowany łańcuch operacji. Zmierz stopień przywoływania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania.

fetch_k = self.k * self.oversampling     # default: 5 × 3 = 15
candidates = vector_store.search(query_embedding, k=fetch_k)

Krok 2 — Obliczanie macierzy odległości

Gdy przechodzisz przez etap Krok 2: Obliczenie odległości, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Zmierz stopień przywoływalności na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania. Gdy przechodzisz przez etap Krok 2: Obliczenie odległości, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całej struktury.

# Normalize all vectors so dot product = cosine similarity
query_norm = query_vec / np.linalg.norm(query_vec)
cand_norm = candidate_matrix / np.linalg.norm(candidate_matrix, axis=1, keepdims=True)

# Distance = 1 - cosine_similarity
query_distances = 1.0 - np.dot(query_norm, cand_norm.T)        # (1, N)
document_distances = 1.0 - np.dot(cand_norm, cand_norm.T)      # (N, N)

Krok 3 — Przekształcenie na prawdopodobieństwa log-normalne

Etap przekształcenia z Kroku 3 działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zanim rozszerzysz zakres, zapisz jeden idealny przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań. Zdokumentuj razem ścieżkę pomyślnego działania oraz ścieżkę przywracania stanu. Próby ponowne, kontrola ludzka oraz obsługa wiadomości błędowych stanowią część produktu, a nie elementy dodawane później. Oddziel zasadę dzielenia na fragmenty od zasady wyszukiwania. Zmiana jednej nie powinna zmuszać do przepisywania drugiej w przypadku zmian wskaźników jakości.

def lognorm(dist, sigma):
    return -np.log(sigma) - 0.5 * np.log(2 * np.pi) - dist**2 / (2 * sigma**2)

Krok 4 — Pętla selekcji chciwej

Krok 4: Etap chciwości działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden „złoty” przepis, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Wolno preferować małe, testowalne jednostki zamiast rozbudowanych skryptów. Gdy jakiś krok zawiedzie, awaria powinna wskazywać na konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Oddziel zasadę dzielenia na fragmenty od zasady pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

# Step 1: pick most relevant first
most_relevant_idx = np.argmax(query_probs)
selected_indices = [most_relevant_idx]
max_distances = doc_probs[most_relevant_idx].copy()    # diversity tracker

# Step 2-6: iteratively add diverse + relevant chunks
while len(selected_indices) < num_results:
    # For each candidate, compute "diversity from any selected"
    updated_distances = np.maximum(max_distances, doc_probs)

    # Combine relevance + diversity
    combined = (diversity_weight * updated_distances
                + relevance_weight * query_probs[np.newaxis, :])

    # Aggregate per candidate (logsumexp for numerical stability)
    normalized = logsumexp(combined, axis=1)

    # Mask already-selected
    for idx in selected_indices:
        normalized[idx] = -np.inf

    # Pick the best
    best_idx = np.argmax(normalized)
    max_distances = updated_distances[best_idx]
    selected_indices.append(best_idx)

Co tak naprawdę robi matematyka (intuicyjnie)

Etap „What the math is” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadań. Oddziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości. Etap „What the math is” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zapisz jeden idealny przykład działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, składysek z danymi poufnymi oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez konieczności czytania całej struktury.

Relevance →
            Low              ●◯

                   ●         ●           ←  Standard top-k picks these 3
                                              (highest relevance, regardless of diversity)
                    ●        ●
                       ●●●●●●  ●  ●  ●     ← Many similar high-relevance chunks
            High         (cluster)


            Diversity ↓
            from
            selected
                        ↓
                     ↓     ↓   ←  Dartboard picks 1 from cluster,
                                 then far-away ones with high relevance still

Masy – co robi każda z nich

Dla każdego etapu należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Należy udokumentować zarówno prawidłowy przebieg procesu, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Należy podać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

relevance_weight = 1.0     # how much we care about chunks being close to query
diversity_weight = 1.0     # how much we care about chunks being different from each other

Przykład zastosowania: test korpusu duplikatów

Dla przykładu A należy najpierw zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Lepiej używać małych, łatwych do przetestowania jednostek niż rozbudowanych skryptów. Gdy dany krok zawiedzie, powinno to wskazywać na konkretną przyczynę, a nie na skomplikowaną strukturę procesu. Należy podawać konkretne fragmenty tekstu, które stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić fałszywych informacji od braków w indeksowaniu.

1. "Greenhouse gases cause warming..."  (sim 0.91)
2. "Greenhouse gases cause warming..."  (sim 0.91)  ← DUPLICATE
3. "Greenhouse gases cause warming..."  (sim 0.91)  ← DUPLICATE
4. "Greenhouse gases cause warming..."  (sim 0.91)  ← DUPLICATE
5. "Greenhouse gases cause warming..."  (sim 0.91)  ← DUPLICATE

Unique results: 1/5
1. "Greenhouse gases cause warming..."  (highest relevance — wins first pick)
2. "Deforestation reduces the carbon sink..."  (different chunk, still relevant)
3. "Industrial agriculture emits methane..."  (third unique cause)
4. "Land-use changes alter surface albedo..."  (fourth unique cause)
5. "Fossil fuel combustion is the largest CO₂ source..."  (related to #1 but different angle)

Unique results: 5/5

Istota w kilku linijach

Aby zrozumieć istotę danej etapy, należy określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie wykonać dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Traktuj tę etapę jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nazwij poszczególne elementy, zdefiniuj kryteria sukcesu i odrzuć ciche, częściowe ukończenie zadania. Podaj fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

def dartboard_select(query_emb, candidate_embs, k=5, sigma=0.1):
    # 1. Compute distance matrices
    query_dist = 1 - cosine(query_emb, candidate_embs)        # query→each
    doc_dist   = 1 - cosine(candidate_embs, candidate_embs)   # each→each

    # 2. Convert distances to log-probabilities
    query_probs = lognorm(query_dist, sigma)
    doc_probs   = lognorm(doc_dist, sigma)

    # 3. Pick most relevant first
    selected = [np.argmax(query_probs)]
    max_distances = doc_probs[selected[0]].copy()

    # 4. Iteratively add diverse + relevant
    while len(selected) < k:
        updated = np.maximum(max_distances, doc_probs)
        combined = updated + query_probs[np.newaxis, :]   # equal weights = sum
        scores = logsumexp(combined, axis=1)
        for idx in selected:
            scores[idx] = -np.inf       # don't re-select

        best = np.argmax(scores)
        max_distances = updated[best]
        selected.append(best)

    return selected

Aby zrozumieć istotę danego etapu, należy określić dane wejściowe, osobę odpowiedzialną za ten etap oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten etap na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny poufnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całej struktury.

Narzędzia, których możesz użyć

Podczas pracy nad elementami sterującymi warto najpierw zapisać umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Dokumentuj razem ścieżkę prawidłowego działania oraz ścieżkę naprawczą. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości błędnych stanowią część produktu, a nie elementy dodawane później. Zmierz stopień odzyskiwania informacji na ustalonej grupie pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania.

Gdzie to się sprawdza, a gdzie nie

Gdy pracujesz nad rozdziałem „Gdzie to uzyskuje swoją fazę”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Wolij małe, testowalne jednostki od rozbudowanych skryptów. Gdy jakiś krok się nie powiedzie, błąd powinien wskazywać na jedną konkretne odpowiedzialność, a nie na skomplikowany łańcuch operacji. Zmierz stopień przywoływania informacji na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy wyszukiwania.

Główna myśl, którą warto zabrać ze sobą

Gdy przechodzisz przez etap „The bigger idea worth”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Traktuj ten etap jako umowę pomiędzy danymi wejściowymi a zweryfikowanymi wynikami. Nadaj nazwy poszczególnym elementom, zdefiniuj kryteria sukcesu i odrzuć przypadkowe, częściowe ukończenie zadań. Zmierz stopień przywoływania informacji na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe możliwości wyszukiwania informacji. Gdy przechodzisz przez etap „The bigger idea worth”, najpierw zapisz umowę: wymagane dane wejściowe, sygnał sukcesu oraz to, co dzieje się w przypadku częściowego niepowodzenia. Taka lista kontrolna zapewnia uczciwość późniejszych zmian w kodzie. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą audytować bez konieczności czytania całej struktury.

Ostatnia myśl

Faza „Ostatnia myśl” działa najlepiej, gdy traktuje się ją jako mierzalną powierzchnię. Zapisz jeden idealny przypadek, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia działań, zanim rozszerzysz zakres pracy. Zdokumentuj zarówno ścieżkę pomyślnej realizacji, jak i ścieżkę przywracania stanu. Próby ponowne, kontrola przez ludzi oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później. Oddziel zasadę dzielenia na fragmenty od zasady wyszukiwania. Zmiana jednej nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.

Lista kontrolna operacyjna

Dla fazy Listy kontrolnej operacyjnej zdefiniuj dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie wykonać dany krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Zapisz czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy ścieżka przechodzi z środowiska demonstracyjnego do wspólnych środowisk.

Należy podać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

Napisz krótki przewodnik: jak rotować klucze, jak opróżnić kolejkę z zadań, jak cofnąć ostatnie operacje importu.

Zachowaj konfigurację poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności przeglądania całej struktury.

Należy podać fragmenty tekstu, które faktycznie stanowią podstawę odpowiedzi. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.

Zanim zastosujesz nową wersję stacku, zamroź aktualne wersje, utwórz „złoty” zapis dla kluczowych ścieżek działania i potwierdź kroki konieczne do cofnięcia zmian. Środowiska współdzielone wymagają ograniczeń szybkości, weryfikacji przynależności użytkowników oraz wyraźnego właściciela odpowiedzialnego za rotację tajnych danych. Wolisz nudną niezawodność niż pomysłowe, jednorazowe demonstracje.

Uwagi dotyczące fd4991fea9d9: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj transkrypcje obok plików testowych, aby późniejsze zmiany modeli pozostały porównywalne.