Jak zbudowałem generator CV z AI
Za kulisami ZłóżCV: dlaczego zacząłem od własnego bólu z dopasowywaniem CV pod oferty, jakie decyzje techniczne podjąłem (Gemini, Puppeteer, SQLite, Next) i co poszło nie tak po drodze.

ZłóżCV zacząłem budować, bo sam miałem dość. Aplikowałem na oferty w IT i za każdym razem powtarzał się ten sam rytuał: kopiuj poprzednie CV, wytnij projekty, które tu nie pasują, dopisz te, które pasują, przepisz bio pod stack z ogłoszenia, popraw kolejność umiejętności. Pół godziny na jedną aplikację, a wysyłałem ich dziesiątki.
Najgorsze było to, że ta robota była powtarzalna, ale nie na tyle, żeby zrobić ją raz a dobrze. Każda oferta kładła nacisk na coś innego: raz Kafka, raz testy, raz „komunikacja z biznesem". Jedno uniwersalne CV przegrywało z każdym, bo nie trafiało w konkret.
Więc zrobiłem narzędzie, które robi to za mnie: wklejasz ofertę, a ono dobiera personę i projekty, pisze bio i opisy pod ten konkretny stack, i wypluwa gotowy dwukolumnowy PDF. Poniżej opisuję, jak to działa pod maską i gdzie się przy tym wyłożyłem.
Parsowanie oferty: pierwszy kontakt z AI
Pierwszy krok to zrozumienie oferty. Wklejony tekst leci do Gemini, które wyciąga z niego strukturę: nazwa stanowiska, wymagany stack, mile widziane technologie, poziom (junior, mid, senior), słowa klucze. Z luźnego ogłoszenia robi się JSON, na którym da się dalej pracować.
Brzmi prosto, ale to tu zobaczyłem pierwszą prawdę o pracy z modelami: ogłoszenia są chaotyczne. Wymagania mieszają się z benefitami, „Java" pojawia się w trzech kontekstach, a sekcja „kogo szukamy" bywa lukrem bez konkretu. Parser musiał być odporny na ten bałagan, a nie zakładać, że oferta jest ładnie ustrukturyzowana.

Dobór persony i projektów z bazy
Tu jest sedno pomysłu. Nie generuję CV z powietrza. Mam bazę person (różne profile zawodowe) i przypisane do nich projekty z konkretnymi technologiami i opisami. Po sparsowaniu oferty AI dobiera najlepiej pasującą personę i wybiera z bazy pięć projektów, które najmocniej grają z wymaganiami.
To świadoma decyzja przeciwko halucynacjom. Gdybym kazał modelowi „wymyślić doświadczenie", dostałbym ładne, ale zmyślone CV. Zamiast tego model nie wymyśla faktów, tylko wybiera i porządkuje to, co realnie jest w bazie. Halucynacja w CV to ryzyko wpadki na rozmowie, więc tej granicy pilnuję najmocniej.
Generowanie treści: bio, opisy, skills
Mając personę i pięć projektów, odpalam generację treści. Model pisze bio pod konkretną ofertę, przepisuje opisy projektów tak, by podkreślały technologie z ogłoszenia, i układa listę umiejętności w kolejności od najbardziej pożądanych.
Kluczowa decyzja kosztowa i wydajnościowa: opisy wszystkich pięciu projektów generuję jednym wsadowym wywołaniem, nie pięcioma osobnymi. Jedno wywołanie zbija narzut na tokeny i skraca czekanie. O rozbiciu kosztów napisałem osobno w ile kosztuje wygenerowanie CV przez AI.
Do tego doszedł czat per projekt i per CV, żeby nanieść poprawki bez generowania wszystkiego od nowa. „Skróć to bio", „dodaj nacisk na Dockera". To działa na function calling i tu trafiłem na najbardziej upierdliwą wpadkę całego projektu.
Czemu taki stack: Gemini, Puppeteer, SQLite, Next
Stack dobierałem pod solo dev, nie pod konferencyjny slajd. Cztery decyzje:
- Gemini 3.5 Flash do parsowania, generacji, refinera i czatu. Robota jest dobrze ustrukturyzowana, więc szybszy i tańszy model w zupełności wystarcza. Nie każdy krok potrzebuje najdroższego modelu na rynku.
- Puppeteer do PDF. CV składam jako stronę HTML w osobnym route group z własnym
<html><body>, renderuję ją headless Chrome i drukuję do dwukolumnowego PDF. Mam pełną kontrolę nad layoutem przez CSS, zamiast walczyć z biblioteką do generowania PDF z kodu. - SQLite + Drizzle zamiast chmurowej bazy. Mniej ruchomych części, zero kosztów stałych za bazę, prosty backup. Na obecnej skali to świadomie nudny wybór, i dobrze.
- Next.js 16 (App Router) na całość: UI, API, routing print page. Jedno repo, jeden deploy w Dockerze za Caddy. Bez mikroserwisowego cyrku dla projektu, który prowadzę sam.

Największe wpadki
Pierwsza: function calling w Gemini 3. Model w odpowiedzi z wywołaniem funkcji dokłada pole, które trzeba odesłać z powrotem w całości razem z resztą jego odpowiedzi. Pominąłem to przy pierwszym podejściu i dostawałem twarde 400 bez sensownego komunikatu. Pół wieczora zeszło, zanim zrozumiałem, że trzeba echo całej zawartości odpowiedzi modelu, a nie tylko wyniku funkcji.
Druga: print page wpadała do PDF razem z sidebarem aplikacji. Puppeteer drukował wszystko, co było na stronie. Rozwiązanie to osobny route group z własnym <html><body>, oderwany od layoutu UI. Oczywiste z perspektywy czasu, mniej oczywiste o północy.
Trzecia, najważniejsza: jakość zależy od promptu bardziej niż od modelu. Pierwsze bio brzmiały jak generyczny ChatGPT, „dynamiczny i zorientowany na cel". Dopiero precyzyjne prompty z twardymi zasadami (zero pustych przymiotników, konkret zamiast frazesów, trzymaj się faktów z bazy) dały treść, którą sam wysłałbym do rekrutera.
Czego się nauczyłem
- Zanim zoptymalizujesz prompt, policz, ile razy go wołasz. Batch zamiast pętli wywołań rozwiązał koszt i czas naraz. Architektura wywołań biła pojedynczy prompt.
- AI ma wybierać, nie wymyślać. Dobór person i projektów z bazy zamiast generowania doświadczenia z powietrza to różnica między narzędziem a generatorem ściemy.
- Czytaj dokumentację modelu, nie pamięć. Function calling w Gemini 3 ma haczyk, którego nie zgadniesz. Godzina w docsach była tańsza niż wieczór debugowania 400.
Zobacz efekt
Najprościej zobaczyć to na własnym CV. Wklejasz ofertę, dostajesz dopasowane CV pod ten konkretny stack, poprawiasz czatem co trzeba. Wypróbuj ZłóżCV, dwa pierwsze za darmo, bez karty. Jak konkretnie dopasować CV pod ofertę w IT, rozpisałem w tym poradniku, a jeśli interesuje cię sama warstwa wizualna, mam wpis o sześciu układach szablonów.
Najczęstsze pytania
Czy dane z CV są bezpieczne? Dane trzymam w SQLite na własnym serwerze, a baza jest user-scoped, czyli każdy widzi tylko swoje rzeczy. Tekst oferty i treści CV przechodzą przez model AI w trakcie generacji, ale nie są publiczne ani udostępniane. Mniej ruchomych części to też mniej miejsc, w których coś może wyciec.
Czemu Gemini, nie GPT? Bo do tego zadania wystarcza. Robota jest dobrze ustrukturyzowana (parsuj, dobierz, napisz pod schemat), więc szybszy i tańszy model daje dobry wynik bez przepłacania. To nie ideologia, tylko dopasowanie narzędzia do zadania. Gdyby jakość siadła, zmieniłbym model bez sentymentu.
Czy AI nie wymyśli mi doświadczenia, którego nie mam? Nie, bo tak to zaprojektowałem. Model nie generuje faktów z powietrza, tylko dobiera personę i projekty z bazy i przepisuje istniejące opisy pod ofertę. Halucynacja w CV to ryzyko wpadki na rozmowie, więc tę granicę pilnuję najmocniej.
Ile trwa wygenerowanie jednego CV? Od wklejenia oferty do gotowego PDF to kilkanaście sekund. Batchowanie opisów projektów w jedno wywołanie skróciło ten czas względem pierwszej wersji, która generowała każdy projekt osobno.