Budzisz się w poniedziałek rano z pięcioma krytycznymi raportami o błędach. Twoje narzędzie do monitorowania analizy wykonało swoją pracę. Wyłapało każdy raport o awarii, każdą wściekłą opinię jednogwiazdkową i każdy komentarz typu „aplikacja zawiesza się, gdy klikam zapisz”. Dokładnie wiesz, co nie działa. Nie wiesz jednak, gdzie szukać.

To była ściana, w którą uderzyłem po zbudowaniu mojego pierwszego pipeline'u. Monitorował on recenzje aplikacji i napływające logi błędów bez żadnych problemów, segregując każdą informację zwrotną do uporządkowanych kategorii: błędy, awarie lub prośby o nowe funkcje. Dashboard wyglądał zdrowo. Sam proces debugowania – nie.

Wiedza o tym, że błąd istnieje, to zaledwie ułamek drogi do jego naprawy. Wciąż musiałem otwierać IDE, przeszukiwać moduły za pomocą grep, konfrontować stack trace'y z aktualną bazą kodu i rekonstruować ścieżkę awarii w swojej głowie. Gdy bilety (tickets) piętrzą się jeden po drugim, a kawa wciąż jest gorąca, ta manualna archeologia pożera czas, którego nie masz. Potrzebowałem, aby pipeline robił coś więcej niż tylko flagowanie problemów. Potrzebowałem, aby je badał.

Zbudowałem więc system od nowa wokół jednego celu: wziąć surowy raport o błędzie i zwrócić zweryfikowaną diagnozę. Nie akapit rozważań LLM, lecz ustrukturyzowane znalezisko, które wskazuje plik, podaje linię, szacuje ryzyko i sugeruje poprawkę. Oto jak to zrealizowałem.

Dlaczego struktura wygrywa z logiem czatu

Agent śledczy został zbudowany przy użyciu PydanticAI. Powód był prosty. Gdy prosisz model językowy o analizę kodu, jego domyślnym wynikiem jest przyjazny strumień tekstu. Może to pomóc ludzkiemu czytelnikowi, ale jest bezużyteczne dla skryptu przetwarzającego dane dalej. Potrzebowałem kontraktu czytelnego dla maszyn.

Agent zwraca zweryfikowany model danych z czterema konkretnymi polami: przyczyną źródłową (root cause), dotkniętymi plikami, proponowanymi zmianami oraz oceną złożoności i ryzyka. Jeśli model pominie pole lub wygeneruje nieistniejącą ścieżkę pliku, walidacja kończy się niepowodzeniem, a ja natychmiast to wyłapuję. Ta rygorystyczność sprawia, że pipeline pozostaje wiarygodny.

Aby przeprowadzić właściwe śledztwo, agent otrzymuje cztery narzędzia w trybie read-only i nic więcej. Może przeszukiwać kod za pomocą grep, czytać konkretne zakresy linii z pliku, listować zawartość katalogów i lokalizować symbole, takie jak klasy czy funkcje. Tryb read-only jest kluczowy. Nie chciałem agenta z uprawnieniami do zapisu, który błąkałby się po moim repozytorium o 2 nad ranem. Najpierw zrozum, potem edytuj.

Mapa repozytorium: Kontekst przed narzędziami

Pierwsza wersja agenta była dokładna, ale niezwykle kosztowna. Przepalał tokeny niczym turysta chodzący w kółko. Model wywoływał list-dir, potem grep, potem czytał plik, a następnie ponownie list-dir, powoli budując mentalny model struktury projektu – jeden kosztowny token po drugim.

Rozwiązaniem było wygenerowanie zwięzłej mapy repozytorium, zanim agent w ogóle wystartuje. Ta mapa to skondensowany przegląd repozytorium: kluczowe pliki, ich główne funkcje lub klasy oraz sposób, w jaki łączą się główne moduły. Potraktuj to jak przekazanie agentowi nawigacji GPS zamiast zmuszania go do odkrywania dróg metodą prób i błędów.

Dzięki tej mapie w oknie kontekstowym agent nie marnuje wywołań na sprawdzanie, czy istnieje src/utils/parser.ts. On już zna teren. Kieruje się prosto tam, gdzie unosi się dym. Ta pojedyncza zmiana całkowicie wyeliminowała fazę błądzenia.

Lejek narzędziowy: Wymuszanie konkluzji

Nawet z mapą agent mógł się wahać. Znajdował podejrzany plik, po czym zaczynał wątpić w swoje decyzje, szukał ponownie, czytał kolejny plik, wpadając w nieskończoną pętlę typu „jeszcze tylko jedno sprawdzenie”. Potrzebowałem sposobu na wymuszenie dynamiki działania.

Wdrożyłem trójfazowy lejek narzędziowy, który ogranicza możliwości agenta w miarę postępu prac.

Faza pierwsza to eksploracja. Agent ma pełny dostęp do wszystkich czterech narzędzi. Może przeszukiwać, przeglądać i czytać wszystko, co jest mu potrzebne do odtworzenia błędu w procesie rozumowania.

Faza druga to deep-dive. Gdy agent zidentyfikuje prawdopodobne punkty awarii, traci narzędzia do eksploracji. Może jedynie czytać pliki. Koniec z grepem i listowaniem katalogów. Na tym etapie musi przeanalizować już znaleziony kod i zbudować łańcuch dowodów.

Faza trzecia to output. Wszystkie narzędzia zostają zablokowane. Agent nie może już przeszukiwać bazy kodu. Musi usiąść i napisać raport. Zapobiega to niekończącej się spirali typu „pozwól, że sprawdzę jeszcze jedną rzecz”.

Ten lejek zmniejszył średnią liczbę wywołań narzędzi z ponad czterdziestu na jedną analizę do około dziesięciu. Agent stał się szybszy, tańszy i paradoksalnie bardziej pewny siebie, ponieważ musiał sformułować konkretną konkluzję.

Utrzymanie wymienności backendu

Nie chciałem sztywno wiązać systemu z jednym dostawcą modeli. Używam różnych silników w zależności od zadania. Czasami Claude Code, czasami Grok Build, czasami cokolwiek jest w danej chwili najtańsze. Aby zachować niezależność logiki rdzeniowej od dostawcy, podzieliłem pracę na dwa etapy.

Etap pierwszy to eksploracja. Agent programistyczny, którym może być dowolny zdolny model, czyta mapę repozytorium, korzysta z narzędzi i generuje surowy raport w formacie markdown. To jest ta kosztowna, wymagająca myślenia część.

Etap drugi to strukturyzacja. Tani, szybki model LLM bierze ten markdown i przekształca go w ścisły model Pydantic. Ten etap prawie nie wymaga rozumowania. To tylko ekstrakcja i formatowanie, więc może działać na lekkim sprzęcie.

Dzięki temu, że granica jest wyraźna, mogę podmienić backend bez dotykania logiki walidacji. Raport markdown działa jako uniwersalny adapter między „mózgiem eksploracyjnym” a ustrukturyzowanym wyjściem, którego faktycznie używam.

Co faktycznie zadziałało

Ta konfiguracja zmieniła sposób, w jaki radzę sobie z napływającymi zgłoszeniami. Warstwa klasyfikacji nadal rozróżnia błędy od próśb o nowe funkcje, ale teraz warstwa analizy przejmuje pałeczkę natychmiast po niej. Zanim otworzę edytor, mam już przygotowaną ścieżkę do pliku, zakres linii i proponowaną zmianę. Wciąż wszystko sprawdzam ręcznie. To asystent, a nie autopilot. Ale zbieranie kontekstu, które kiedyś