Opublikowano: 30 kwietnia 2026 r.
Dzięki wbudowanej AI Twoja witryna lub aplikacja internetowa może wykonywać zadania oparte na AI bez konieczności wdrażania modeli, zarządzania nimi ani samodzielnego hostowania. Przejście od wersji demonstracyjnej do funkcji gotowej do wdrożenia produkcyjnego może być trudne. Ten dokument zawiera informacje o kwestiach technicznych i związanych z UX, które pomogą Ci uniknąć typowych błędów.
Wcześniejsze przygotowanie modelu
Dotyczy wszystkich interfejsów API, np. Summarizer, Translator i Writer.
Zrób: zainicjuj sesję, gdy tylko rozpoznasz intencję użytkownika. Ponieważ do zainicjowania sesji wymagana jest aktywacja użytkownika, możesz użyć dowolnej interakcji, np. kliknięcia w dowolnym miejscu na stronie oferującej funkcję opartą na AI. Przygotowuje to model i środowisko wykonawcze, gdy użytkownik wchodzi w interakcję z interfejsem. W odpowiednich przypadkach rozpocznij kolejne najbardziej prawdopodobne zadanie realizowane z wykorzystaniem AI, gdy tylko zaczniesz renderować wynik.
Nie: poczekaj, aż użytkownik kliknie „Wygeneruj”, aby zainicjować sesję. Powoduje to wielosekundowe opóźnienie uruchomienia „na zimno”, ponieważ model musi najpierw zostać wczytany do pamięci i przygotować potok wykonywania.
Ustawianie początkowych promptów podczas tworzenia
Dotyczy: Prompt API.
Zrób to: podaj instrukcje systemowe podczas inicjowania sesji, aby przyspieszyć działanie pierwszego prompta.
Nie: zaczynaj od pustej sesji i wysyłaj instrukcje systemowe w ramach pierwszego wywołania prompt(). Wydłuża to czas oczekiwania, ponieważ zmusza model do przetwarzania tych instrukcji w ostatniej chwili.
// ✅ DO: Create the session as early as possible (tip on warming up the model early) and use initialPrompts for system instructions in the create call
const session = await LanguageModel.create({
initialPrompts: [
{ role: 'system', content: 'You are a helpful assistant specialized in code reviews.' }
]
});
// A few moments later, when the user triggers the AI feature
const review = await session.prompt(`Review the following code:\n\n${code}`);
// ❌ DON'T: Send instructions using prompt() after creation
// const slowerSession = await LanguageModel.create();
// await slowerSession.prompt(`You are a helpful assistant specialized in code reviews.\n\nReview the following code:\n\n${code}`); // Higher latency
Klonowanie sesji w przypadku powtarzalnych zadań
Dotyczy: Prompt API.
W przypadku interfejsu Prompt API każda sesja śledzi kontekst rozmowy, uwzględniając wszystkie poprzednie interakcje. Klon dziedziczy wszystko z sesji nadrzędnej, w tym początkowe prompty i całą historię interakcji do momentu sklonowania, dlatego strukturuj użycie tak, aby dziedziczyć tylko to, czego potrzebujesz.
Zalecane czynności:
- Utwórz sesję podstawową: aby efektywnie wykonywać niezwiązane ze sobą zadania, utwórz sesję podstawową, która zawiera tylko instrukcje systemowe i nie ma wcześniejszego kontekstu rozmowy.
- Sklonuj sesję bazową: użyj ikony
clone()w sesji bazowej, aby wykonać nowe zadania i zaoszczędzić czas potrzebny na ponowne przeanalizowanie złożonych instrukcji systemowych. Umożliwia to tworzenie równoległych rozmów lub resetowanie zadania do stanu początkowego.
Nie rób tego:
- Nie używaj tej samej sesji do niepowiązanych zadań i unikaj klonowania sesji, które zawierają już niepotrzebną historię interakcji. Oba wzorce mogą powodować, że niezwiązany z bieżącym zadaniem kontekst będzie mu przeszkadzać.
- Nie wywołuj wielokrotnie funkcji
create()z identycznymi, złożonymi instrukcjami systemowymi. Aby zoptymalizować wydajność, użyj zamiast tego wzorca klonowania.
// ✅ DO: Create a baseline session and clone it for each new task
const baseSession = await LanguageModel.create({
initialPrompts: [{
role: 'system',
content: 'You are a technical editor...',
}],
});
// Clone the base session once for the first task
const task1 = await baseSession.clone();
const response1 = await task1.prompt("Review this first draft...");
// ... Repeat the cloning pattern for subsequent independent tasks
// Each task starts fresh from the baseline system instructions
// ❌ DON'T:
// Bad performance pattern: repeated create() calls for identical tasks.
// This forces the model to re-parse instructions every time, increasing latency.
// const sessionA = await LanguageModel.create({ initialPrompts: [...] });
// await sessionA.prompt("Task 1...");
// const sessionB = await LanguageModel.create({ initialPrompts: [...] });
// await sessionB.prompt("Task 2...");
// Bad quality pattern: reusing the same session for unrelated tasks.
// const session = await LanguageModel.create();
// await session.prompt("Analyze this financial report...");
// Unrelated task in the same session:
// await session.prompt("Now write a children's story...");
Usuwanie nieużywanych sesji
Dotyczy: wszystkich interfejsów API.
Rób: jawnie wywołuj destroy() w przypadku sesji, które nie są już potrzebne, aby zwolnić pamięć, gdy funkcja nie jest już używana. Jeśli używasz wzorca klonowania, zachowaj sesję podstawową i usuń klony, które nie są już potrzebne.
Nie: utrzymuj aktywnych wielu dużych sesji. Każda sesja zużywa pamięć, co powoduje niepotrzebne wykorzystanie zasobów i może stanowić problem. Sesje zostaną automatycznie usunięte przez moduł odśmiecania pamięci, ale wywołanie funkcji destroy() szybciej zwalnia pamięć.
// ✅ DO: Use the clone and destroy it immediately after
const clone = await baseSession.clone();
const response = await clone.prompt("Quick task...");
// Free memory right away: destry the clone, keep the baseSession
clone.destroy();
Bezpieczne i wydajne renderowanie odpowiedzi przesyłanych strumieniowo
Dotyczy: wszystkich interfejsów API obsługujących przesyłanie strumieniowe (Prompt, Summarizer, Writer, Rewriter i Translator).
Zalecenie: traktuj wszystkie dane wyjściowe LLM jako niezaufane treści. Oczyść cały połączony wynik, a nie tylko fragmenty, ponieważ złośliwy kod może być podzielony na kilka aktualizacji. Przed renderowaniem użyj interfejsu Sanitizer API, jeśli jest obsługiwany. Aby uniknąć spadku wydajności, użyj parsera Markdown do przesyłania strumieniowego, np. streaming-markdown.
Nie: bezpośrednio ustawiaj innerHTML przy każdej aktualizacji fragmentu. Jest to powolne, zwłaszcza w przypadku złożonego formatowania, takiego jak podświetlanie składni, i podatne na wstrzykiwanie.
import * as smd from "streaming-markdown";
// Set up virtual buffer and Sanitizer API
const sanitizer = new Sanitizer({
allowElements: ['figure', 'figcaption', 'p', 'br', 'strong', 'em', 'img', 'a'],
allowAttributes: {
'loading': ['img'], 'decoding': ['img'], 'src': ['img'], 'href': ['a']
}
});
// Create an off-screen fragment so the parser doesn't cause flicker
// or trigger XSS in the live DOM during the building process.
const buffer = new DocumentFragment();
const parser = smd.parser_new(buffer);
// Use sanitizer as a gatekeeper / cleaner function so we can combine it with the streaming Markdown parser
function syncSanitized(target, sourceFragment) {
// .sanitize() returns a fresh, clean DocumentFragment
const cleanFragment = sanitizer.sanitize(sourceFragment);
// replaceChildren is the modern high-performance way to swap DOM content
target.replaceChildren(cleanFragment);
}
// Streaming Logic
// `chunks` keeps track of the raw string (useful for logs/debug)
chunks += chunk;
// Let the parser build the DOM incrementally in the buffer.
// This is high-performance because the buffer is not live
smd.parser_write(parser, chunk);
// Use the Sanitizer API to port the content safely to the container.
syncSanitized(container, buffer);
Optymalizacja wprowadzania pod kątem szybkości
Dotyczy: wszystkich interfejsów API.
Rób: przekazuj modelowi tylko to, co jest absolutnie niezbędne. Usuń wszystko, co nie ma związku z wykonywanym zadaniem. W przypadku dużych zbiorów danych podaj krótki przegląd i mały wybór odpowiednich elementów.
Nie wysyłaj: do interfejsów API nieprzetworzonego tekstu, niepotrzebnych metadanych, tagów HTML ani dużych niefiltrowanych list. Opóźnienie znacznie wzrasta wraz z rozmiarem danych wejściowych, co może sprawić, że funkcja AI będzie wydawać się niedziałająca na wielu urządzeniach.
// ✅ DO: Send only relevant text
const cleanText = document.querySelector('#article').innerText;
const summary = await Summarizer.summarize(cleanText);
// ❌ DON'T: Send the entire DOM structure
// const dirtyText = document.querySelector('#article').innerHTML;
Używanie uporządkowanych danych wyjściowych w celu uzyskania przewidywalnych wyników
Dotyczy: Prompt API.
Rób: jeśli chcesz, aby model zwracał dane w określonym formacie, użyj danych wyjściowych w formacie strukturalnym, podając pole responseConstraint, aby udostępnić schemat JSON. Dzięki temu dane wyjściowe są przewidywalne i nie wymagają złożonego przetwarzania końcowego ani ręcznego parsowania.
Nie: polegaj wyłącznie na instrukcjach w języku naturalnym (np. „wygeneruj tylko JSON”). Modele mogą zawierać wypełniacze konwersacyjne, które powodują błędy w analizatorze.
// ✅ DO: Use a JSON Schema for predictable results
const schema = {
type: "object",
properties: {
isTopicCats: { type: "boolean" }
}
};
const result = await session.prompt(`Is this post about cats?\n\n${post}`, {
responseConstraint: schema,
});
console.log(JSON.parse(result).isTopicCats);
Oddzielenie generowania od ograniczeń długości
Dotyczy: interfejsu Prompt API, ponieważ jest to jedyny interfejs API, który obsługuje schematy danych wyjściowych.
Zrób to: pozwól modelowi wygenerować odpowiedź w naturalny sposób, a następnie użyj logiki po stronie klienta, aby skrócić tekst i dopasować go do interfejsu.
Nie: wymuszaj ścisłych limitów znaków, np. maxLength: 125, za pomocą schematów danych strukturalnych. Gdy odpowiedź modelu jest dłuższa niż ustawiony limit, model może przełączyć się na tokeny o wysokiej gęstości, takie jak języki obce lub emoji, aby skompresować znaczenie, co może skutkować bezsensownymi wynikami.
/* DO: Handle overflow using CSS */
.result {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis; /* Displays '…' */
}
// ❌ DON'T: Force length in the prompt
const result = await session.prompt("Write a bio in exactly 50 characters.");
Zarządzanie cierpliwością użytkownika
Dotyczy: wszystkich interfejsów API.
Zrób: używaj animacji i technik interfejsu, aby zarządzać cierpliwością użytkownika. Optymalne podejście zależy od przypadku użycia i oczekiwanej długości danych wyjściowych interfejsu API. Oto kilka pomysłów:
- Streaming w przypadku długich treści: w przypadku podsumowań lub czatu streaming domyślnie tworzy efekt maszyny do pisania dla każdego tokena. Może to być naturalne i zapewnić natychmiastową informację zwrotną.
- Nieprzesyłanie strumieniowe w przypadku krótkich zadań (lub długich zadań asynchronicznych): w przypadku krótkich wyników, np. tekstu alternatywnego, nieprzesyłanie strumieniowe może zapewnić bardziej dopracowany interfejs. Daje to też czas na spekulacyjne przygotowanie następnego zadania realizowanego z wykorzystaniem AI podczas renderowania bieżącego. To podejście sprawdza się też w przypadku dłuższych zadań asynchronicznych lub działających w tle. Jeśli użytkownik nie jest zablokowany w dalszym korzystaniu z usługi, nie ma pilnej potrzeby generowania odpowiedzi na bieżąco. Sygnalizuj w interfejsie użytkownika, że proces jest w toku.
- Przejścia wizualne w przypadku aktualizacji: podczas tłumaczenia lub przepisywania tekstu używaj animacji, np. przekształcania słów.
Nie: nie aktualizuj interfejsu bez wskazówek wizualnych.
dopasowanie do sposobu, w jaki użytkownik postrzega czas i pracę;
Dotyczy: wszystkich interfejsów API.
Zrób to: rozważ sztuczne opóźnienie o 1–2 sekundy, jeśli odpowiedź jest niemal natychmiastowa. Paradoksalnie użytkownicy mogą uważać wyniki za bardziej wiarygodne, gdy proces generowania jest zgodny z ich postrzeganą trudnością zadania. Używaj animacji, aby sygnalizować, że proces AI został zakończony.
Nie: zaskakuj użytkowników natychmiastowymi zmianami interfejsu.
Zezwalaj użytkownikom na szybkie poruszanie się po zmianach wprowadzonych przez AI i ich cofanie
Dotyczy: wszystkich interfejsów API.
Zrób: wyposaż interfejs w elementy sterujące lub historię nawigacji, które pozwolą użytkownikom swobodnie przeglądać różne wyniki i szybko cofać zmiany wprowadzone przez AI. Dzięki temu różne wersje są nadal łatwo dostępne.
Nie: nadpisuj poprzedniej wersji roboczej użytkownika ani wyniku wygenerowanego przez AI, który mógł mu się spodobać, bez możliwości powrotu, przywrócenia lub porównania wersji.
Umożliwianie użytkownikom kontrolowania ustawień i ich zastępowania
Dotyczy: wszystkich interfejsów API.
Zalecenie: zawsze pozwól użytkownikowi mieć ostatnie słowo. umożliwiać ręczne zastępowanie sugestii; Interfejsy API mogą zwracać nieprawidłowe wyniki.
Nie: nie wymuszaj, aby wynik wygenerowany przez AI był jedyną opcją.
Buforowanie wyników powtarzających się zadań
Dotyczy: wszystkich interfejsów API.
Zrób to: wdróż lokalną pamięć podręczną wyników (np. za pomocą sessionStorage lubIndexedDB) w przypadku powtarzających się danych wejściowych lub zapytań. Znormalizuj dane wejściowe, usuwając białe znaki i zmieniając wielkość liter na małe, aby zwiększyć liczbę trafień w pamięci podręcznej. W przypadku dużych danych wejściowych, np. obrazów, wygeneruj hash, który będzie używany jako klucz pamięci podręcznej. Ustaw konserwatywny czas życia danych (TTL) w pamięci podręcznej (lub wyświetlaj wyniki z pamięci podręcznej podczas aktualizowania ich w tle). Umożliw użytkownikowi wywołanie nowej inferencji, jeśli wynik jest niezadowalający.
Nie: ponownie uruchamiaj tego samego wnioskowania w przypadku powtarzającego się zapytania lub identycznych danych wejściowych, np. gdy użytkownik przechodzi tam i z powrotem między wynikami wyszukiwania. Wnioskowanie na urządzeniu jest bezpłatne pod względem kosztów chmury, ale jest kosztowne pod względem czasu użytkownika i żywotności baterii.
// ✅ DO: Check a local cache before running inference
async function getAiResponse(userInput, forceRefresh = false) {
// Normalize the query to increase cache hits
const query = userInput.trim().toLowerCase();
const cacheKey = `ai_results_${query}`;
const TTL_MS = 3600000; // 1 hour conservative TTL
if (!forceRefresh) {
const itemStr = localStorage.getItem(cacheKey);
if (itemStr) {
const item = JSON.parse(itemStr);
const now = Date.now();
// Check if the item has expired
if (now < item.expiry) {
// Lightweight safety check before rendering
if (isValid(item.value)) return item.value;
} else {
// Delete the stale entry if the TTL has passed
localStorage.removeItem(cacheKey);
}
}
}
// Fallback: Run inference if no valid cache exists
const session = await LanguageModel.create();
const response = await session.prompt(userInput);
// Store the result for future use (with an expiration)
const cacheData = {
value: response,
expiry: Date.now() + TTL_MS
};
localStorage.setItem(cacheKey, JSON.stringify(cacheData));
return response;
}