Tworzenie Lucas AI: przekształcanie portfolio w produkt

← Powrót do bloga

22 kwietnia 20266 min read

Tworzenie Lucas AI: przekształcanie portfolio w produkt

Ograniczony LLM UX: dwuetapowe wnioskowanie, strumieniowanie SSE, strukturalne uziemienie i granice produktu, które utrzymują porządek w portfolio czatu.

produktaifrontend

Większość portfolio przestaje ewoluować. Chciałem, aby moje portfolio zachowywało się trochę jak produkt: i18n-pierwszy, wspólny interfejs użytkownika w monorepozytorium i warstwa Lucas AI, która odpowiada w pierwszej osobie o mojej pracy — tylko z ustrukturyzowanego kontekstu zapiekłego w czasie wdrożenia. Ten post jest dla inżynierów, którzy chcą zobaczyć obraz systemu: kształt wnioskowania, strumieniowanie, uziemienie i wyraźne cele — nie przewodnik po tym, gdzie znajdują się pliki lub jak uruchomić fork lokalnie.

Jaki problem rozwiązuje

Statyczne strony są złe w kontynuacjach. Jeśli twoją przewagą jest osąd — zakres, kompromisy, jak dostarczałeś pod ograniczeniami — PDF i formularz kontaktowy nie skalują ciekawości. Lucas AI jest dedykowanym miejscem docelowym (/[locale]/ai): zapytaj o doświadczenie, stronę lub samą funkcję, bez udawania, że jestem na drugim końcu linii w czasie rzeczywistym.

Architektura wysokiego poziomu

Przeglądarka posiada interfejs użytkownika do transkrypcji; serwer posiada politykę i wydatki. Każde wysłanie jest POST /api/chat z { message, locale } — bez załączników, bez wywołań narzędzi, bez ukrytych pól.

Obrabiacz buduje system, przekazuje do Groq OpenAI-kompatybilnego /v1/chat/completions, i proxyuje strumień upstreamu jako Server-Sent Events (SSE), dzięki czemu tokeny renderują się stopniowo bez buforowania pełnego zakończenia w pamięci na krawędzi. To jest ten sam wzorzec, który można by użyć za dowolnym szybkim interfejsem API wnioskowania: traktuj trasę jako cienki adapter, utrzymuj stabilny format drutu dla klienta.

Uziemienie żyje w kodzie aplikacji: obiekt kariery jest sformatowany raz w CONTEXT string i wstrzyknięty do komunikatu systemowego. Nie ma bazy danych wektorowej i nie ma kroku pobierania w czasie żądania — model widzi tylko to, co zserializowałeś podczas budowania podpowiedzi. To jest kompromis wydajności (nie możesz odpowiedzieć z dowolnych dokumentów) w zamian za przewidywalną latencję, koszt i powierzchnię audytu.

Modele i wywołania

  • Główna odpowiedź: domyślna llama-3.3-70b-versatile na Groq (nadpisz GROQ_MODEL). Strumieniowanie pozostaje włączone; trasa pozwala na 60s generowania — wystarczająco dla ostrożnej odpowiedzi bez przekształcenia krawędzi w nieograniczonego pracownika.
  • Klasyfikator przed lotem: llama-3.1-8b-instant (konfigurowalny jako CLASSIFIER_MODEL). Jest to oddzielne, nie-strumieniowe wywołanie z max_tokens: 5, temperature: 0, i minimalnym podpowiedzią systemową, która redukuje decyzję do jednego tokena: CAREER lub OFF_TOPIC.

Jeśli werdykt jest OFF_TOPIC, API nigdy nie wywołuje dużego modelu. Strumieniuje odmawiającą odpowiedź jako SSE, dzięki czemu kod klienta pasuje do „prawdziwego” zakończenia — bez specjalnej gałęzi UI dla skrócenia.

Co wysyłamy na serwer (i czego nie)

Każde żądanie niesie:

  • message: bieżący obrót użytkownika, przycięty, z twardym limitem 2 000 znaków (zmniejszenie powierzchni nadużyć i wstrzyknięć podpowiedzi).
  • locale: znormalizowany do obsługiwanego lokalizowanego miejsca witryny. Podpowiedź systemowa kończy się z Visitor locale: …, dzięki czemu model odpowiada w języku witryny, a nie w języku, w którym użyty jest interfejs użytkownika.

Czego nie wysyłamy:

  • Brak poprzednich obrotów na drucie. Backend jest jednoturniowy na żądanie: podpowiedź systemowa + jedna wiadomość użytkownika. Wątek, który widzisz w interfejsie użytkownika, nie jest odtwarzany do modelu przy każdym wysłaniu.

Zatem „pamięć” jest: opublikowany CONTEXT + to, co jest nadal widoczne w kliencie. To jest celowa granica: brak historii czatu po stronie serwera, brak synchronizacji między urządzeniami, brak szkolenia na rozmowach.

Gdzie żyje wątek

Interfejs użytkownika utrzymuje wiadomości w sessionStorage (lucas-ai-messages). Odświeżenie w tej samej karcie utrzymuje wątek; nowa karta lub urządzenie zaczyna się od nowa. Istnieje drugi klucz, lucas-ai-pending, używany podczas przekierowania użytkownika po zmianie lokalizacji (więcej poniżej).

Jeśli GROQ_API_KEY jest pusty, trasa nadal zwraca SSE: strumieniuje zlokalizowany ciąg błędu konfiguracji, dzięki czemu powłoka nie wyrzuca układu.

Strukturalne uziemienie (nie RAG)

Korpus kariery zaczyna się jako typowe rekordy — role, stosy, inicjatywy, metryki, kind (praca vs wolontariat vs edukacja vs osobisty), zakresy czasowe i pola narracyjne. Formatownik przekształca to w jeden blok markdownopodobny zawarty w wyraźnych ogranicznikach, np.:

--- CONTEXT (prawda; nie zaprzeczaj ani nie rozszerzaj poza nią) ---

--- Koniec kontekstu ---

To, co się dzieje, ma takie samo znaczenie jak to, co pozostaje poza:

  • Biografia, mocne strony, zasady, styl pracy — orientacja bez szumu CV.
  • Doświadczenie — według pracodawcy: zakres, inicjatywy (co / wpływ / dowód), metryki i opcjonalne fragmenty historii context / problem / rozwiązanie / wpływ, gdy chcesz, aby model mówił o wynikach, a nie o buzzwordach.
  • Podsumowanie czasu — miesięcy kalendarzowych dla zawodowej pracy z regułami, które zapobiegają podwójnemu liczeniu nakładających się okresów lub przemycaniu projektów hobbystycznych do „lat doświadczenia”.
  • Opis „produkt + implementacja” — nie marketingowa kopia, ale fakty, które jesteś gotów bronić: kształt hostingu, podejście i18n, jak czat jest wywoływany, strumieniowanie, zachowanie klasyfikatora, gdzie żyje stan, postawa prywatności. Celem tego fragmentu jest odpowiedź na pytania meta z tego samego obiektu gruntownej prawdy, co pytania dotyczące kariery — dzięki czemu „jak działa ta funkcja?” nie staje się otwartym czekiem dla modelu, aby wymyślać drzewa plików lub zależności.

Na górze system jest surowym prawem: pierwsza osoba, brak udawania bycia na żywo na Slacku, brak dostępu do prywatnych systemów, brak trzeciej osoby „Lucas powiedział…”, CONTEXT jako jedyne źródło faktyczne i brak fabrykacji poza tym, co CONTEXT stwierdza dla szczegółów wdrożenia.

To jest sposób, aby „nie halucynować” od wibracji do testowalnego zakresu: model jest tak mądry, jak pakiet, który dostarczasz, a pakiet jest wersjonowany jak kod.

Klasyfikator tematyczny (co jest w zakresie)

Zadaniem małego modelu jest tylko routing, a nie pomocność. CAREER jest zdefiniowany szeroko: tło, umiejętności, dostarczona praca, osąd produktu, i uzasadnione pytania o samą stronę — stos, potok lokalizacji, jak asystent jest okablowany — w zakresie, w jakim istnieją informacje w CONTEXT. OFF_TOPIC łapie wszystko inne (pogoda, praca domowa, niezwiązane trywialności).

Traktowanie pytań „meta” jako w zakresie jest decyzją produktową: asystent portfolio powinien wyjaśnić warunki graniczne bez otwierania całej sieci jako źródła wiedzy.

Język wejściowy a lokalizacja strony (po stronie klienta)

Lokalizacja trasy napędza język odpowiedzi (przez podpowiedź). Ale użytkownicy czasami piszą w innym języku, pozostając np. na angielskim interfejsie użytkownika.

Podczas wysyłania klient uruchamia franc-min na wejściu (minimalna długość ~15 znaków). Jeśli wykryty język nie pasuje do bieżącego lokalizowanego miejsca witryny, nie wysyła cicho do API. Pokazuje kartę ofert: przyciski do router.push(/${targetLocale}/ai) dla pasujących lokalizacji, plus „kontynuuj w bieżącym języku”. Jeśli przełączają lokalizację, schowek przechowuje wiadomość w sessionStorage, nawiguje, a następnie automatycznie wysyła po zamontowaniu, dzięki czemu pytanie jest uruchamiane z odpowiednim locale w ciele JSON.

To jest zachowanie produktu: wyrównaj język witryny z językiem, w którym użytkownik faktycznie pisze, zamiast zmuszać model do zgadywania lub mieszania polityk.

Umieszczenie UX

Lucas AI jest miejscem docelowym nawigacji i sekcją domową (odznaką, nagłówkiem, wezwaniem do działania, przykładowymi podpowiedziami) — nie pływającym widżetem nad przepływem czytania. Czat na pełnej stronie utrzymuje wzorzec wybierz i unika „niespodzianego pilota” anty-wzorca, gdzie generatywny interfejs użytkownika walczy z resztą układu o uwagę.

Tryby awaryjne, o których zadbaliśmy

  • Klasyfikator w dół: przejdź do głównego modelu (awaria otwarta).
  • Groq 401/403/400: zwróć JSON llm_auth do klienta — bez surowego ciała upstreamu (unikaj wycieku klucza lub wskazówek modelu).
  • 5xx / 429: strumień zlokalizowanego ogólnego błędu za pomocą fałszywego SSE, gdy jest to właściwe.
  • Pusta lub zbyt duża wiadomość: 400 z stabilnym kodem błędu.

Podsumowanie

Lucas AI nie jest ogólnym asystentem rzuconym na stronę marketingową. Jest to wąski produkt: jeden korpus faktów, za który się opowiadasz, jedna ścieżka odpowiedzi strumieniowej, mały model, który decyduje tylko o „w zakresie lub nie”, i serwer, który celowo zapomina każdego obrotu. Celem jest przewidywalne zachowanie — to, co jest wysyłane do dostawcy, co płacisz za API wnioskowania za żądanie, i to, co odwiedzający może traktować jako faktyczne.

Jeśli zbudujesz coś takiego, dźwignią nie jest wybranie największego modelu. To traktowanie podpowiedzi systemowej i CONTEXT jak specyfikacji — prostej, faktycznej, wierszowej — a nie kopii marketingowej, i decydowanie w architekturze, kiedy duży model może być uruchomiony w ogóle (na przykład tylko po bramie tematycznej).