Uwagi praktyczne: Seria AI: Najtrudniejsza część RAG, Część 2 — Dlaczego doskonałość
Krok po kroku praktyczne wskazówki: Seria AI: Najtrudniejsza część RAG, Część 2 — Dlaczego doskonałość: umowy, sprawdzania oraz miejsca na kod do wstawienia dla zespołów wdrażających ten wzorzec.
To przewodnik pokazuje, jak odbudować proces od surowców do działającego systemu w ramach: Seria AI: Najtrudniejsza część RAG, Część 2 — Dlaczego nawet doskonałe fragmenty danych nadal powodują słabe wyniki wyszukiwania. Skupiamy się na krokach operacyjnych, wyraźnych sprawdzeniach oraz kodzie, który można bez problemu wdrożyć do repozytorium, bez konieczności zgadywania intencji. Na etapie przeglądu 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 systemu. Należy rejestrować czas wykonywania operacji oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy proces przechodzi z środowiska demonstracyjnego do współdzielonych środowisk.
Najpierw, czym właściwie jest embedding?
Gdy przechodzisz przez pierwszy etap „Co to jest?”, 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.
"How do I reset my password?" → [0.81, 0.12, -0.44, 0.09]
"Steps to change your password" → [0.79, 0.15, -0.41, 0.11]
"How do I cancel my subscription?"→ [0.22, 0.68, 0.05, -0.39]
from numpy import dot
from numpy.linalg import norm
def cosine_similarity(a, b):
return dot(a, b) / (norm(a) * norm(b))
query = [0.80, 0.13, -0.42, 0.10] # "reset my password"
print(cosine_similarity(query, [0.81, 0.12, -0.44, 0.09])) # 0.998 - very close
print(cosine_similarity(query, [0.22, 0.68, 0.05, -0.39])) # 0.310 - far apart
Dlaczego wyszukiwanie jest naprawdę trudne
Gdy przechodzisz przez etap „Dlaczego wyszukiwanie jest rzeczywiście skuteczne”, 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 prawidłowy przebieg, jak i ścieżkę naprawczą. Próby ponownych wyszukiwań, kontrola przez ludzi oraz obsługa wiadomości błędowych są częścią produktu, a nie elementem późniejszej dopracowywania. Zmierz stopień przywoływalności na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe funkcje wyszukiwania.
Jak wygląda rzeczywisty pipeline wyszukiwania
Gdy przechodzisz przez etap „Co to jest prawdziwe odzyskiwanie informacji”, 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 konkretną odpowiedzialność, a nie na skomplikowany łańcuch operacji. Zmierz stopień odzyskiwania informacji na ustalonej serii pytań przed dostosowywaniem promptów. Częste zmiany promptów rzadko naprawiają słabe mechanizmy odzyskiwania danych. Gdy przechodzisz przez etap „Co to jest prawdziwe odzyskiwanie informacji”, 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ń. Wczesna widoczność kosztów zapobiega nieoczekiwanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do wspólnych środowisk.
Szukanie hybrydowe: Dlaczego sama szukanie wektorowa nie wystarcza
Szukanie hybrydowe działa najlepiej, gdy traktuje się je jako mierzalną powierzchnię. Zapisz jeden idealny przypadek, jeden przypadek niepowodzenia oraz notatkę dotyczącą cofnięcia działań przed rozszerzeniem zakresu. Przechowuj konfigurację poza kodem aplikacji. Pliki środowiskowe, skrytki z danymi poufnymi oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić bez konieczności czytania całego grafu. Oddziel zasadę dzielenia na fragmenty od zasady wyszukiwania. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.
from collections import defaultdict
def reciprocal_rank_fusion(result_lists, k=60):
scores = defaultdict(float)
for result_list in result_lists:
for rank, doc_id in enumerate(result_list):
scores[doc_id] += 1 / (k + rank + 1)
return sorted(scores, key=scores.get, reverse=True)
vector_ids = [d.metadata["chunk_id"] for d in vector_store.similarity_search(query, k=20)]
bm25_ids = [d.metadata["chunk_id"] for d in bm25_index.search(query, k=20)]
final_ranking = reciprocal_rank_fusion([vector_ids, bm25_ids])
Ponowna sortowanie to krok, którego nikt nie pomija dwukrotnie
Reranking Is the Step funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia. Zanim rozszerzysz zakres, zapisz jeden udany przypadek działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian. Zdokumentuj zarówno prawidłowy przebieg działania, jak i ścieżkę naprawczą. Próby ponownych działań, kontrola przez ludzi 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 z nich nie powinna zmuszać do przepisywania drugiej w przypadku zmian wskaźników jakości.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-base")
def rerank(query, candidates, top_n=5):
pairs = [(query, c["text"]) for c in candidates]
scores = reranker.predict(pairs)
for c, s in zip(candidates, scores):
c["rerank_score"] = float(s)
return sorted(candidates, key=lambda c: c["rerank_score"], reverse=True)[:top_n]
Poprawianie pytania, a nie tylko indeksu
Etap „Fixing the Question Not” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, 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 zasady dzielenia na części od zasad pobierania danych. Zmiana jednych nie powinna zmuszać do przepisywania drugich, gdy zmieniają się metryki jakości. Etap „Fixing the Question Not” funkcjonuje najlepiej, gdy traktowany jest jako mierzalna powierzchnia do analizy. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, zanim rozszerzysz zakres pracy. Zapisuj czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy przechodzi się z środowiska demonstracyjnego do współdzielonych środowisk.
def hyde_retrieve(query, llm, vector_store, k=5):
hypothetical = llm.invoke(f"Write a short passage answering: {query}")
return vector_store.similarity_search(hypothetical, k=k)
Mierzenie zamiast oceny na oko
Dla etapu „Mierzenie zamiast” należy określić dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed modyfikacją kodu. Operatorzy powinni móc ponownie uruchomić ten krok na podstawie znanego punktu kontrolnego, bez konieczności zgadywania ukrytego stanu. Konfigurację należy przechowywać poza kodem aplikacji. Pliki środowiskowe, magazyny tajnych danych oraz flagi funkcjonalne powinny znajdować się w jednym miejscu, które operatorzy mogą sprawdzić, nie musząc czytać całej struktury. Należy podawać konkretne fragmenty tekstu, na których opiera się odpowiedź. Bez tych odniesień operatorzy nie będą w stanie odróżnić halucynacji od luki w indeksowaniu.
def recall_at_k(retrieved_ids, relevant_ids, k):
top_k = set(retrieved_ids[:k])
return len(top_k & relevant_ids) / len(relevant_ids) if relevant_ids else 0.0
Co powiedziałbyś osobie, która rozpoczyna pracę nad tym dzisiaj
W fazie „Co powiesz?” należy zdefiniować dane wejściowe, osobę odpowiedzialną za dany krok oraz kryteria zakończenia przed zmianą kodu. Operatorzy powinni móc ponownie uruchomić ten 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 nieodebranych 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.
Nieprzyjemna prawda o RAG
W ramach etapu „The Uncomfortable Truth About” 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 preferować małe, łatwe do przetestowania jednostki nad rozbudowanymi skryptami. Gdy dany krok zawiedzie, powinien wskazywać na konkretną odpowiedzialność, a nie na skomplikowany proces. 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. W ramach etapu „The Uncomfortable Truth About” 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 rejestrować czasy wykonywania oraz koszt tokenów lub zapytań obok wyników funkcjonalnych. Wczesna widoczność kosztów zapobiega niespodziewanym rachunkom, gdy proces przechodzi z wersji demonstracyjnej do wersji dostępnej dla wszystkich.
środowiskach.List kontrolny operacyjny
Etap listy kontrolnej operacyjnej działa najlepiej, gdy traktuje się go jako mierzalną powierzchnię. Zapisz jeden idealny przepis działania, jeden przypadek awarii oraz notatkę dotyczącą cofnięcia zmian, 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ń.
Rozdziel politykę dzielenia na fragmenty od polityki pobierania danych. Zmiana jednej z nich nie powinna zmuszać do przepisywania drugiej, gdy zmieniają się metryki jakości.
Oceniaj odpowiedzi jednoetapowe oraz ścieżki wieloetapowe oddzielnie. Agregacja ocen z rozmów maskuje błędy w pętlach narzędziowych.
Napisz krótki przewodnik: jak rotować klucze, jak opróżnić kolejkę z zadań, jak cofnąć ostatnie wprowadzone dane.
Zdokumentuj zarówno ścieżkę prawidłowego działania, jak i ścieżkę przywracania. Próby ponownych działań, mechanizmy ludzkiej kontroli oraz obsługa wiadomości nieodebranych stanowią część produktu, a nie elementy dodawane później.
Zanim wdrożysz cały zestaw narzędzi, zamroź wersje produktu, utwórz dokładny zapis działań dla krytycznej ścieżki oraz potwierdź kroki konieczne do cofnięcia zmian. Środowiska współdzielone wymagają ograniczeń szybkości działania, weryfikacji uprawnień użytkowników oraz wyraźnego odpowiedzialnego za rotację haseł. Wolisz nudną niezawodność od pomysłowych, jednorazowych demonstracji.
Uwaga dotycząca pliku da3fc7392f6d: unikaj przechowywania kluczy dostawcy w repozytorium, ustaw ograniczenie liczby tokenów na sesję oraz przechowuj zapisy działań obok plików konfiguracyjnych, aby późniejsze zmiany modeli pozostały porównywalne.
Literatura pokrewna
- Praktyczne notatki: Mas skalowania RAG do 10 milionów dokumentów Część 2: Optymalizacja — Szczegółowy przewodnik po Praktycznych notatkach: Mas skalowania RAG do 10 milionów dokumentów Część 2: Optymalizacja: szablony kontraktów, sprawdzenia oraz miejsca na kod do wklejenia dla zespołów wdrażających ten wzorzec.
- Praktyczne notatki: Recepta Spring AI: Filtrowanie wyników RAG z metadanymi — Szczegółowy przewodnik po Praktycznych notatkach: Recepta Spring AI: Filtrowanie wyników RAG z metadanymi: szablony kontraktów, sprawdzenia oraz miejsca na kod do wklejenia dla zespołów wdrażających ten wzorzec.