Blog

Alerty z Jenkinsa, które stawiają diagnozę: n8n i Langfuse w praktyce QA

Kategoria:

Mail o treści «Build #452 Failed» przerywa mi pracę i nie mówi nic. Żeby znaleźć przyczynę, muszę zalogować się do Jenkinsa, otworzyć konsolę i przewinąć kilka tysięcy linii logu. Przy testach aplikacji opartych o LLM jest jeszcze gorzej: asercja «odpowiedź modelu niezgodna z faktami» wygląda w logu identycznie jak literówka w konfiguracji. Zbudowałem więc system z trzech narzędzi (Jenkinsa, n8n i Langfuse), który zamiast surowego statusu wysyła na Slacka gotową diagnozę: co padło, w której linii i ile kosztowały tokeny w nieudanej sesji testowej.

Trzy narzędzia, trzy role

Każde narzędzie w tym układzie robi jedną rzecz:

  • Jenkins pozostaje wołem roboczym: uruchamia buildy i testy, a po zakończeniu zadania wysyła webhook z surowymi danymi. Nic więcej.
  • n8n odbiera webhook i pracuje jako warstwa analityczna: decyduje, czy błąd zasługuje na alert, dociąga fragmenty logów i skleja wiadomość dla zespołu.
  • Langfuse przechowuje przebiegi (trace’y) testów LLM. Gdy pipeline padnie na ewaluacji modelu, n8n odpytuje jego API o szczegóły konkretnego przebiegu.

Ten podział ma praktyczną zaletę: logikę raportowania zmieniam w workflowie n8n, bez dotykania Jenkinsfile i bez przepychania zmiany konfiguracji przez kolejny deploy.

Krok 1: Jenkins wysyła kontekst, a nie sam status

Wbudowane wtyczki Jenkinsa do Slacka odpadły u mnie na starcie, bo generują dokładnie ten bezużyteczny komunikat, od którego uciekam. Zamiast nich w Jenkinsfile dodaję blok post { always { ... } }, który po każdym buildzie wysyła do n8n obiekt JSON:

{
  "jobName": "backend-llm-service",
  "buildNumber": 452,
  "status": "FAILURE",
  "gitCommit": "a1b2c3d",
  "langfuseTraceId": "${env.LANGFUSE_TRACE_ID}",
  "buildUrl": "https://jenkins.firma.pl/job/backend-llm-service/452/"
}

Zmienna LANGFUSE_TRACE_ID robi tu całą robotę. Generuję ją na początku pipeline’u i wstrzykuję w dwa miejsca: do testów, żeby Langfuse zapisał przebieg pod tym identyfikatorem, oraz do payloadu powiadomienia. Bez niej nie połączę później czerwonego builda z konkretną sesją testową modelu.

Krok 2: n8n odszumia sygnał

Workflow w n8n nie przekazuje webhooka dalej bez analizy. Węzłem HTTP Request odpytuję zwrotnie Jenkinsa o końcówkę logu konsoli (endpoint /consoleText), a prosty regex wyławia z ostatnich 50 linii słowa kluczowe: Exception, Timeout, AssertionError. Do tego dochodzi filtr kontekstu, bo awaria nocnego builda na gałęzi feature zasługuje na niższy priorytet niż czerwony main. Alert brzmi wtedy «ModuleNotFoundError w linii 45, commit a1b2c3d» i od razu wiadomo, kto ma się nim zająć.

Drugi mechanizm odszumiania to pamięć stanu. Workflow zapisuje ostatni status każdego joba i alertuje przy zmianie: przejściu z zielonego na czerwony oraz z powrotem. Niestabilny test, który piąty raz z rzędu wali tym samym błędem, dostaje jedną wiadomość z licznikiem powtórzeń zamiast pięciu osobnych pingów. Kanał na Slacku przestaje być szumem, który zespół nauczył się wyciszać.

Taki workflow składam w kilka godzin. Jak wygląda ta praca u klientów, od webhooków po integracje z komunikatorami, opisuję na stronie o automatyzacji procesów w n8n.

Krok 3: Langfuse dopowiada to, czego nie widać w logach

Log CI/CD jest bezradny wobec błędów AI. Gdy test wykryje, że chatbot zmyśla albo odpowiada za wolno, Jenkins pokaże suche «Assertion Failed». Dlatego workflow ma warunek: jeśli job dotyczy serwisu AI i status to FAILURE, n8n odpytuje API Langfuse po zapisanym wcześniej traceId i dokleja do alertu:

  • koszt tokenów całej sesji testowej,
  • latencję odpowiedzi modelu (rozstrzyga, czy timeout to wina infrastruktury, czy modelu),
  • wynik ewaluacji, np. Hallucination Score: 0.8.

Langfuse przydaje mi się zresztą szerzej, niż sugeruje jego metka «observability dla LLM». Numer builda traktuję jako identyfikator trace’a, każdy stage Jenkinsa zapisuję jako osobny span, a status końcowy mapuję na score. Po miesiącu mam przeszukiwalną historię: filtruję buildy, które padły na etapie testów E2E, i widzę na osi czasu, że «Docker Build» trwał cztery minuty zamiast standardowych trzydziestu sekund.

Anatomia alertu, który coś mówi

Na końcu n8n skleja wiadomość (Block Kit dla Slacka albo karta dla MS Teams) z czterech elementów:

  • Nagłówek: nazwa serwisu, numer builda i status.
  • Diagnoza: wyciągnięty z logu konkretny błąd, bez ściany tekstu.
  • Sekcja AI (gdy jest traceId), koszt, latencja, wynik ewaluacji.
  • Przyciski akcji: «Otwórz w Jenkins», «Analizuj w Langfuse» i «Re-run», czyli webhook zwrotny, który uruchamia build ponownie prosto z komunikatora.

Ta konstrukcja zmieniła u mnie obsługę awarii z grzebania w logach na czytanie gotowego raportu incydentu, który przychodzi kilka sekund po zdarzeniu. Monitorowanie to przy tym ostatnie ogniwo łańcucha: żeby alerty miały co raportować, pipeline musi najpierw sensownie uruchamiać testy. Jak go ustawiam od zera, opisałem w tekście o testach automatycznych w pipeline CI/CD.