Zespół backendowy w firmie SaaS ma 180 000 linii kodu, 40 runbooków w Confluence i referencję API generowaną automatycznie z Swagger. Nowy programista spędza pierwszy tydzień głównie na pytaniach w stylu: „gdzie jest obsługiwany ten błąd?", „jak działa autoryzacja dla endpointu X?" i „co robi ta flaga?". Starsi koledzy wiedzą. Problem polega na tym, że ta wiedza nie jest nigdzie zapisana w formie, z której można wyszukać.
My w Cashcrown badamy, jak RAG nad repozytoriami i dokumentacją techniczną działa w praktycznych wdrożeniach. Poniższy artykuł opisuje różnice wobec RAG nad prozą, podejście do chunkingu symbolicznego, rolę wyszukiwania hybrydowego i granice tego, co wymaga decyzji programisty.
Dlaczego kod i dokumentacja techniczna to inne wyzwanie niż proza
#Dokumenty HR, procedury czy FAQ mają strukturę liniową. Splitter oparty na znakach lub tokenach daje sensowne fragmenty, bo zdanie wyrwane z kontekstu jest wciąż zdaniem.
Kod ma strukturę symboliczną. Funkcja calculate_discount(order_id, user_tier) jest jednostką semantyczną. Przerwie jej definicji w połowie i dostaniesz fragment bez sygnatury albo fragment bez ciała, każdy bezużyteczny osobno. To samo dotyczy klas, metod, bloków try/except i dekoratorów.
Dokumentacja techniczna jest hybrydą: runbook zawiera listę kroków odwołujących się do konkretnych komend i zmiennych środowiskowych, wiki API opisuje endpointy z dokładnymi typami parametrów. Wyszukiwanie po słowach kluczowych działa tu znacznie lepiej niż przy swobodnej prozie. Do tego dochodzi zmienność: RAG indeksujący raz na miesiąc cytuje sygnatury, które przestały istnieć dwa tygodnie wcześniej.
Chunking po symbolach, nie po akapitach
#Dobra strategia chunkingu kodu opiera się na granicach symboli, nie tokenów. Parser AST (Abstract Syntax Tree) wyodrębnia funkcje, klasy i metody jako zamknięte jednostki. Biblioteki takie jak tree-sitter obsługują ponad 40 języków i zwracają granice każdego symbolu jako numery linii.
Praktyczny wzorzec: chunk = jeden symbol z docstringiem, sygnaturą i ciałem. Metadane obowiązkowe to file_path, symbol_name, start_line, end_line, last_commit_sha. Dla dokumentacji technicznej reguła jest prostsza: dziel po nagłówkach sekcji (H2/H3), nie po liczbie tokenów. Runbook podzielony po krokach daje fragmenty samowystarczalne proceduralnie.
Tabela poniżej zestawia podejście do chunkingu dla różnych typów zasobów:
| Typ zasobu | Strategia chunkingu | Metadane obowiązkowe | Orientacyjny rozmiar |
|---|---|---|---|
| Kod źródłowy (Python, TS, Go) | AST po granicach symbolu | file_path, symbol_name, start_line, end_line, last_commit_sha | 50–300 linii na symbol |
| Referencja API (OpenAPI/Swagger) | Jeden endpoint = jeden chunk | method, path, operationId, version | 200–600 tokenów |
| Runbook / procedura operacyjna | Recursive po nagłówkach H2/H3 | tytuł sekcji, service_name, last_modified | 300–800 tokenów |
| Wiki wewnętrzna (proza techniczna) | Recursive z overlap 10–15% | page_title, page_url, author, last_modified | 512–1024 tokeny |
| Changelog / commit messages | Fixed-size lub cały wpis | commit_sha, author, date, branch | 128–256 tokenów |
Więcej o strategiach chunkingu w ogólności opisuje artykuł chunking dokumentów do RAG.
Dlaczego wyszukiwanie hybrydowe jest tu niezbędne
#Przy dokumentach prosaistycznych wyszukiwanie czysto wektorowe radzi sobie dobrze, bo zapytania są semantyczne: „jak obsłużyć reklamację?" trafia do odpowiedniego akapitu nawet bez dosłownego pokrycia słów.
Przy kodzie zapytania często zawierają dokładne identyfikatory: UserRepository.findByEmail, POST /v1/orders/{id}/cancel, CASHCROWN_API_JWT_SECRET. Wyszukiwanie semantyczne (embedding oparte) nie radzi sobie z rzadkimi tokenami. Model widzi UserRepository jako ciąg znaków bez szerokiego znaczenia semantycznego i trafia na ogólne fragmenty zamiast na definicję klasy.
BM25 to odwrócony indeks oparty na częstości słów. Dla identyfikatorów z kodem źródłowym działa jak wyszukiwarka pełnotekstowa: znajdzie UserRepository.findByEmail dokładnie tam, gdzie ta metoda jest zdefiniowana lub wywołana. Połączenie BM25 z wyszukiwaniem wektorowym przez wyszukiwanie hybrydowe (RRF lub liniowe ważenie wyniku) podnosi recall dla zapytań z dokładnymi nazwami bez pogarszania wyników dla zapytań semantycznych.
W testach wewnętrznych Cashcrown na zbiorze 12 000 symboli z projektu komercyjnego recall@5 przy czystym wyszukiwaniu wektorowym wynosił 61%. Po dodaniu BM25 w trybie hybrydowym wzrósł do 79%. Różnica widoczna była głównie przy zapytaniach zawierających sygnatury metod i ścieżki plików. To orientacyjne liczby z jednego projektu, nie gwarancja powtarzalności na innym kodzie.
Po retrieval warto dodać warstwę rerankingu, która ponownie sortuje top-k fragmentów pod kątem trafności do konkretnego zapytania. Dla kodu to ważne, bo BM25 może awansować fragmenty z nazwą identyczną leksykalnie, ale z innego modułu lub przestrzeni nazw. Reranker ocenia pełny kontekst, nie tylko obecność tokenu. Szczegóły porównania baz wektorowych, które obsługują hybrid search natywnie, opisuje artykuł jak wybrać bazę wektorową.
Świeżość indeksu: problem nie do zignorowania
#Kod ma inny cykl życia niż procedura HR. Procedura urlopowa zmienia się raz na rok. Sygnatura metody w aktywnym projekcie zmienia się co sprint.
RAG nad kodem, który nie odświeża indeksu, produkuje odpowiedzi z deprecatedymi API. Programista, który dostanie przykład użycia funkcji usuniętej dwa commity temu, traci czas na debugowanie błędu wygenerowanego przez asystenta.
Trzy poziomy odświeżania, które stosujemy w zależności od szybkości zmian projektu:
Przyrostowa reindeksacja po commicie. Webhook z GitLab/GitHub (lub hak post-receive) wyzwala reindeksację tylko plików zmienionych w commicie. git diff --name-only HEAD~1 HEAD daje listę plików. Każdy przechodzi przez parser AST ponownie. Symbole, których granice nie zmieniły się, pozostają w indeksie; zmienione zastępują poprzednie wersje.
Wersjonowanie chunków przez commit SHA. Każdy chunk przechowuje last_commit_sha w metadanych. Przy zapytaniu można filtrować po gałęzi (branch: main), co pozwala odpowiadać zarówno o aktualnym kodzie, jak i o konkretnej wersji historycznej.
TTL dla dokumentacji. Runbooki i wiki nie mają webhooka. Ustawiamy TTL po którym dokument jest automatycznie reindeksowany: 24-48 godzin dla aktywnie zmieniającej się dokumentacji, 7-14 dni dla stabilnych specyfikacji.
Asystent powinien ujawniać datę ostatniej indeksacji dokumentu w cytacie. „Źródło: src/auth/user_service.py, linia 142, indeksowano 2 godziny temu" daje programiście sygnał, czy może ufać odpowiedzi. Pominięcie tej informacji to ukryte ryzyko.
Cytowanie źródła jako wymóg, nie opcja
#W przypadku dokumentów prose'owych cytowanie nazwy pliku i numeru strony to dobra praktyka. Dla kodu to wymóg bezpieczeństwa.
Programista, który dostaje odpowiedź „funkcja calculate_discount jest w module pricing", nie może jej zastosować bez weryfikacji. Może istnieć kilka modułów pricing w monorepo. Może metoda w ostatnim commicie zmieniła sygnaturę. Może asystent pomylił podobnie nazwaną metodę z innego modułu.
Odpowiedź użyteczna technicznie wygląda tak: „calculate_discount(order_id: int, user_tier: str) -> Decimal (plik: src/billing/pricing.py, linia 87, commit a3f9c12, indeksowano 40 minut temu)". Programista klika link do pliku, widzi aktualny kod, weryfikuje sygnaturę i dopiero stosuje.
Guardrails dla RAG nad kodem powinny blokować dwie klasy odpowiedzi:
Brak pokrycia w indeksie. Jeśli retrieval nie zwrócił żadnego fragmentu z wynikiem podobieństwa powyżej progu (orientacyjnie 0,65–0,75 dla modelu wyszukiwania semantycznego), asystent odpowiada: „nie znalazłem tej sygnatury w zaindeksowanej wersji kodu". To lepsze niż próba rekonstrukcji z pamięci modelu, która wyprodukuje nieistniejące API.
Stary indeks. Jeśli metadane chunka wskazują, że plik nie był reindeksowany od więcej niż X godzin (próg zależny od tempa zmian projektu), odpowiedź zawiera ostrzeżenie o potencjalnej nieaktualności.
Więcej o tym, jak RAG nad bazą wiedzy obsługuje cytowanie i guardrails, w artykule firmowy GPT na bazie wiedzy.
Przypadki użycia: gdzie asystent pomaga najbardziej
#Trzy przypadki, które spotykamy najczęściej w projektach, i jaka jest rola człowieka w każdym z nich:
Onboarding programisty. Nowy członek zespołu pyta: „gdzie jest obsługiwana autoryzacja OAuth?". Asystent zwraca fragment src/auth/oauth_handler.py z linią startową, docstringiem i nazwą commitującego. Programista czyta kod. Asystent skraca czas do pierwszego sensownego pytania do starszego kolegi, ale nie zastępuje przeglądu kodu.
Pytania „gdzie jest X zaimplementowane?". Na etapie code review lub debugowania: „gdzie jest zaimplementowany mechanizm retry dla błędów 503?". Asystent zwraca listę miejsc w kodzie, które pasują semantycznie i leksykalnie. Programista decyduje, które z nich jest właściwym miejscem dla danego kontekstu. Asystent nie wie nic o zamiarze architektonicznym, który jest w głowie architekta.
Odpowiadanie na pytania supportowe z dokumentacji. Klient supportu pyta o parametr API. Asystent szuka w zaindeksowanej referencji OpenAPI i zwraca opis z numerem wersji specyfikacji. Support weryfikuje w Swagger UI przed udzieleniem odpowiedzi. Asystent redukuje czas szukania, nie eliminuje weryfikacji.
We wszystkich trzech przypadkach rola człowieka jest nieusuwalna: decyzje architektoniczne, stosowanie kodu w produkcji i odpowiedzi, gdzie błąd ma realne konsekwencje, zawsze wymagają ludzkiego osądu. Asystent RAG nad kodem to narzędzie nawigacyjne, nie autorytet.
Szerszy kontekst wyszukiwania hybrydowego dla technicznych baz wiedzy opisuje artykuł hybrydowe wyszukiwanie BM25 i wektory.
FAQ
#Czy RAG nad kodem może proponować gotowe fragmenty do wklejenia?
#Może zwrócić istniejący fragment kodu z repozytorium jako cytat. To różni się od generowania nowego kodu: asystent pokazuje, jak dany wzorzec jest już zrealizowany w projekcie, z podaniem pliku i linii. Programista ocenia, czy fragment nadaje się do ponownego użycia w nowym kontekście. Asystent nie powinien generować kodu, którego nie ma w bazie, bo ryzyko zmyślenia nieistniejącej sygnatury jest wysokie.
Jak obsłużyć pytania o kod, który nie jest jeszcze zaindeksowany?
#Gdy retrieval zwraca brak wyników lub wyniki poniżej progu podobieństwa, guardrail powinien odpowiedzieć wprost: „nie znalazłem pokrycia dla tego pytania w zaindeksowanej wersji repozytorium". Asystent może zaproponować alternatywne zapytanie albo wskazać, że dany plik mógł nie być jeszcze zaindeksowany. Zgadywanie sygnatury funkcji na podstawie wiedzy ogólnej modelu to najczęstsza przyczyna błędnych odpowiedzi w tego typu systemach.
Jak często powinienem reindeksować dokumentację techniczną w Confluence?
#Zależy od tempa zmian. Runbooki dla aktywnie rozwijanych usług warto reindeksować co 24 godziny. Stabilna specyfikacja architektoniczna może mieć TTL 7-14 dni. Kluczowe jest informowanie asystenta o dacie ostatniej indeksacji w każdej odpowiedzi, żeby użytkownik wiedział, czy cytowane treści są aktualne. Stary indeks bez ostrzeżenia jest gorszy niż brak indeksu.
Dlaczego wyszukiwanie czysto wektorowe nie wystarcza dla kodu?
#Model embeddingowy reprezentuje znaczenie semantyczne, ale nazwy funkcji, klas i zmiennych to rzadkie tokeny bez szerokiego kontekstu znaczeniowego w ogólnym korpusie. Wyszukiwanie czysto wektorowe będzie awansować ogólne fragmenty na temat uwierzytelniania zamiast konkretnej klasy AuthService. BM25 traktuje identyfikatory jak słowa kluczowe i trafia na nie dokładnie. Połączenie obu przez wyszukiwanie hybrydowe obsługuje zarówno zapytania semantyczne, jak i leksykalne.
Co zrobić, gdy asystent cytuje sygnaturę, która już nie istnieje?
#To sygnał, że indeks jest nieaktualny. Reakcja powinna być dwuetapowa: po stronie użytkownika, weryfikacja pliku źródłowego przed zastosowaniem, bo to zawsze jest wymagane. Po stronie systemu, skrócenie TTL reindeksacji dla zmieniających się plików i dodanie ostrzeżenia w odpowiedzi, gdy metadane wskazują na stary commit SHA. Nigdy nie stosuj kodu zwróconego przez asystenta bez sprawdzenia aktualnej wersji w repozytorium.