
← Powrót do bloga
Ograniczony LLM UX: dwuetapowe wnioskowanie, strumieniowanie SSE, strukturalne uziemienie i granice produktu, które utrzymują porządek w portfolio czatu.
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.
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.
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.
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.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.
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:
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.
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.
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:
context / problem / rozwiązanie / wpływ, gdy chcesz, aby model mówił o wynikach, a nie o buzzwordach.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.
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.
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.
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ę.
llm_auth do klienta — bez surowego ciała upstreamu (unikaj wycieku klucza lub wskazówek modelu).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).