Atgal į žurnalą
14 min. skaitymo

Nulinės delsos DI įrenginyje su React: Chrome Gemini Nano ir Prompt API gamyboje

React sąsajų kūrimas be delsos ir su visišku privatumu, naudojant Chrome integruotas Gemini Nano API, window.LanguageModel, vietinį PII valymą ir hibridinius atsarginius kelius.

React.jsTypeScriptIn-Browser LLMsAI ArchitectureGDPR & PII Management
Nulinės delsos DI įrenginyje su React: Chrome Gemini Nano ir Prompt API gamyboje

Nulinės delsos DI įrenginyje su React: Chrome Gemini Nano ir Prompt API gamyboje

Kiekvienas klavišo paspaudimas šiuolaikinėje saityno programoje reikalauja akimirksniu pateikiamo atsako. Kai komandos prijungia didžiuosius kalbos modelius prie teksto laukų automatiniam užbaigimui, tono derinimui ar santraukų generavimui realiuoju laiku, jos susiduria su neišvengiama fizine riba: tinklo užklausos kelionės laiku.

Užklausa iš naršyklės į debesijos LLM šliuzą užtrunka nuo 300 ms iki 1500 ms, kol gaunamas pirmasis žetonas (TTFT). Toks vėlavimas tinka atskiram pokalbių langui, tačiau sutrikdo teksto rinkimo ritmą redaktoriuose ir formose. Debesijos modelių naudojimas taip pat sukelia pasikartojančias žetonų išlaidas, šaltus startus, užklausų limitus ir atitikties rizikas, kai jautrūs naudotojų duomenys palieka kliento įrenginį.

Chrome integruotas DI keičia šią situaciją integruodamas Gemini Nano tiesiai į naršyklės vykdymo aplinką. Šiame straipsnyje aptariama architektūra, sesijų gyvavimo ciklas, duomenų valymas kliento pusėje ir konkretūs React integravimo modeliai vietiniams modeliams gamyboje paleisti.

CODE
+-------------------------------------------------------------------------+
| Browser Context (Main Thread)                                           |
|                                                                         |
|  [ User Input ] ---> [ Local PII Sanitizer ] ---> [ useBrowserAI Hook ] |
|                             |                              |            |
|                             v                              v            |
|                     (Cleaned Prompt)             (Streaming Tokens)     |
|                                                            |            |
|                                                            v            |
|                                                  [ <SmartTextArea /> ]  |
+------------------------------------------------------------|------------+
                                                             | IPC / Mojo
+------------------------------------------------------------v------------+
| Chrome Optimization Guide Process (On-Device Runtime)                   |
|                                                                         |
|  [ window.LanguageModel / ai ] <---> [ Gemini Nano Neural Weights ]     |
|  - Sub-20ms TTFT                     - Zero Network Egress              |
|  - Local Session Cache               - GPU/NPU Silicon (LiteRT/Metal)   |
+-------------------------------------------------------------------------+

Naršyklėje veikiančio modelio struktūra

Chrome pateikia Gemini Nano kaip įrenginyje veikiantį neuroninį bazinį modelį, kurį valdo naršyklės Optimization Guide paslauga. Skirtingai nuo WebAssembly ar WebGPU sprendimų, reikalaujančių atsisiųsti nuo 1 GB iki 4 GB modelio parametrų pirmojo puslapio įkėlimo metu, Chrome binarinį failą fone atsiunčia per savo Component Updater.

Modelio skaičiavimai vykdomi izoliuotame pagalbiniame procese, atskirtame nuo saityno turinio scenarijų. JavaScript vykdymo kontekstas sąveikauja su modeliu per struktūrizuotą tarpprocesinį ryšį (IPC), naudodamas standartinę window.LanguageModel globaliąją API (ir pereinamąją window.ai.languageModel vardų sritį).

Architektūrinės savybės

  1. Griežtas privatumas: Neapdorotas užklausos tekstas niekada nekerta tinklo lizdo. Griežtai reguliuojamuose sektoriuose, pavyzdžiui, sveikatos apsaugos ar finansų, vietinis modelio vykdymas atitinka reikalavimus be duomenų nutekėjimo.
  2. TTFT mažesnis nei 20 ms: Kadangi modelis yra vietinėje atmintyje ir naudoja įrenginio aparatinės įrangos spartintuvus (Apple Silicon Metal, Windows DirectML, Vulkan), žetonų generavimas prasideda praktiškai be vėlavimo.
  3. Nulinės kintamos infrastruktūros išlaidos: Skaičiavimai atliekami naudotojo įrenginyje. Net šimtų tūkstančių naudotojų srautai sukuria 0 $ išlaidų DI paslaugų API.
  4. Darbas be interneto ryšio: Funkcijos veikia be trikdžių esant nestabiliam ryšiui ar visiškam tinklo dingimui.

Chrome integruoto DI gyvavimo ciklas

Prompt API nėra paprastas būsenos neturintis funkcijos iškvietimas. Ji remiasi būseną išlaikančiu sesijos modeliu, kuriam reikalingas išankstinis galimybių patikrinimas, parametrų derinimas ir atminties valdymas.

TYPESCRIPT
// Tipų aprašai W3C Prompt API
export type AICapabilityAvailability = "readily" | "after-download" | "no";

export interface AICapabilities {
  available: AICapabilityAvailability;
  defaultTemperature?: number;
  maxTemperature?: number;
  defaultTopK?: number;
  maxTopK?: number;
}

export interface AILanguageModelCreateOptions {
  systemPrompt?: string;
  initialPrompts?: Array<{ role: "system" | "user" | "assistant"; content: string }>;
  temperature?: number;
  topK?: number;
  signal?: AbortSignal;
  monitor?: (monitor: EventTarget) => void;
}

export interface AILanguageModelSession {
  prompt(input: string, options?: { signal?: AbortSignal }): Promise<string>;
  promptStreaming(input: string, options?: { signal?: AbortSignal }): ReadableStream<string>;
  countPromptTokens(input: string): Promise<number>;
  maxTokens: number;
  tokensSoFar: number;
  tokensLeft: number;
  topK: number;
  temperature: number;
  clone(): Promise<AILanguageModelSession>;
  destroy(): void;
}

export interface AILanguageModelFactory {
  availability?(): Promise<AICapabilityAvailability>;
  capabilities?(): Promise<AICapabilities>;
  create(options?: AILanguageModelCreateOptions): Promise<AILanguageModelSession>;
}

declare global {
  interface Window {
    LanguageModel?: AILanguageModelFactory;
    ai?: {
      languageModel?: AILanguageModelFactory;
    };
  }
}

Trys modelio pasiekiamumo būsenos

Prieš pradedant sesiją, programos tikrina window.LanguageModel (arba window.ai.languageModel). Naršyklė grąžina vieną iš trijų būsenų:

  • "readily": Modelio binarinis failas yra išsaugotas diske ir paruoštas naudoti iš karto.
  • "after-download": Įrenginys atitinka aparatūros reikalavimus, tačiau modelis laukia atsisiuntimo fone. Programos gali stebėti eigą naudodamos monitor atgalinį iškvietimą.
  • "no": Įrenginys neturi reikiamos aparatūros (trūksta VRAM/RAM arba nepalaikoma vaizdo plokštė) arba funkcionalumas naršyklėje yra išjungtas.

PII valymo sluoksnis kliento pusėje

Net naudojant įrenginyje veikiantį modelį be duomenų siuntimo į tinklą, gerosios saugumo praktikos reikalauja užmaskuoti jautrius duomenis (kredito kortelių numerius, asmens kodus, el. pašto adresus, autorizacijos raktus) prieš formuojant užklausą. Tai apsaugo nuo duomenų persidengimo bendrose sesijose.

Pateikiamas paprastas žetonizavimo filtras be išorinių bibliotekų:

TYPESCRIPT
// lib/pii-scrubber.ts

interface ScrubRule {
  name: string;
  pattern: RegExp;
  mask: (match: string) => string;
}

const PII_RULES: ScrubRule[] = [
  {
    name: "CREDIT_CARD",
    pattern: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
    mask: () => "[REDACTED_CARD]",
  },
  {
    name: "EMAIL",
    pattern: /[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+/g,
    mask: () => "[REDACTED_EMAIL]",
  },
  {
    name: "PHONE",
    pattern: /\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g,
    mask: () => "[REDACTED_PHONE]",
  },
  {
    name: "AUTH_TOKEN",
    pattern: /\b(ey[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*)|(ghp_[A-Za-z0-9]{36})\b/g,
    mask: () => "[REDACTED_TOKEN]",
  },
  {
    name: "SSN",
    pattern: /\b\d{3}-\d{2}-\d{4}\b/g,
    mask: () => "[REDACTED_SSN]",
  },
];

export interface ScrubResult {
  sanitizedText: string;
  redactionCount: number;
  detectedTypes: string[];
}

export function sanitizePromptText(input: string): ScrubResult {
  let sanitizedText = input;
  let redactionCount = 0;
  const detectedTypes: string[] = [];

  for (const rule of PII_RULES) {
    const matches = sanitizedText.match(rule.pattern);
    if (matches && matches.length > 0) {
      redactionCount += matches.length;
      detectedTypes.push(rule.name);
      sanitizedText = sanitizedText.replace(rule.pattern, rule.mask);
    }
  }

  return { sanitizedText, redactionCount, detectedTypes };
}

Gamybai pritaikytas React kabliukas: useBrowserAI

Rankinis modelio sesijų valdymas React komponentuose gali sukelti atminties nutekėjimą, jei sesijos nėra atlaisvinamos išmontavimo metu. useBrowserAI kabliukas valdo gyvavimo ciklą, galimybių tikrinimą, teksto srautinį kaupimą ir užklausų nutraukimą.

TYPESCRIPT
// hooks/useBrowserAI.ts
"use client";

import { useState, useEffect, useRef, useCallback } from "react";
import type {
  AICapabilityAvailability,
  AILanguageModelSession,
  AILanguageModelCreateOptions,
} from "@/types/chrome-ai";

interface UseBrowserAIOptions extends AILanguageModelCreateOptions {
  autoInit?: boolean;
}

interface UseBrowserAIReturn {
  availability: AICapabilityAvailability | "checking" | "unsupported";
  downloadProgress: number | null;
  isGenerating: boolean;
  error: string | null;
  generateText: (promptText: string) => Promise<string>;
  streamText: (promptText: string, onChunk: (chunk: string) => void) => Promise<string>;
  abort: () => void;
  resetSession: () => Promise<void>;
  tokensRemaining: number | null;
}

export function useBrowserAI(options: UseBrowserAIOptions = {}): UseBrowserAIReturn {
  const { systemPrompt, temperature = 0.7, topK = 3, autoInit = true } = options;

  const [availability, setAvailability] = useState<AICapabilityAvailability | "checking" | "unsupported">("checking");
  const [downloadProgress, setDownloadProgress] = useState<number | null>(null);
  const [isGenerating, setIsGenerating] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [tokensRemaining, setTokensRemaining] = useState<number | null>(null);

  const sessionRef = useRef<AILanguageModelSession | null>(null);
  const abortControllerRef = useRef<AbortController | null>(null);

  const cleanupSession = useCallback(() => {
    if (sessionRef.current) {
      try {
        sessionRef.current.destroy();
      } catch (err) {
        console.warn("Error destroying previous session:", err);
      }
      sessionRef.current = null;
    }
  }, []);

  const initSession = useCallback(async () => {
    if (typeof window === "undefined") return;

    const factory = (window as any).LanguageModel || (window as any).ai?.languageModel;
    if (!factory) {
      setAvailability("unsupported");
      return;
    }

    try {
      let availStatus: AICapabilityAvailability = "readily";
      if (typeof factory.availability === "function") {
        availStatus = await factory.availability();
      } else if (typeof factory.capabilities === "function") {
        const caps = await factory.capabilities();
        availStatus = caps.available;
      }
      
      setAvailability(availStatus);

      if (availStatus === "no") {
        return;
      }

      cleanupSession();

      const session = await factory.create({
        systemPrompt,
        temperature,
        topK,
        monitor(m: EventTarget) {
          m.addEventListener("downloadprogress", (e: Event) => {
            const customEvent = e as CustomEvent<{ loaded: number; total: number }>;
            if (customEvent.detail && customEvent.detail.total > 0) {
              const progress = Math.round((customEvent.detail.loaded / customEvent.detail.total) * 100);
              setDownloadProgress(progress);
            }
          });
        },
      });

      sessionRef.current = session;
      setTokensRemaining(session.tokensLeft ?? 4096);
      setError(null);
    } catch (err) {
      const message = err instanceof Error ? err.message : "Failed to initialize Browser AI";
      setError(message);
      cleanupSession();
    }
  }, [systemPrompt, temperature, topK, cleanupSession]);

  useEffect(() => {
    if (autoInit) {
      initSession();
    }
    return () => {
      cleanupSession();
    };
  }, [autoInit, initSession, cleanupSession]);

  const abort = useCallback(() => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      abortControllerRef.current = null;
    }
    setIsGenerating(false);
  }, []);

  const streamText = useCallback(
    async (promptText: string, onChunk: (chunk: string) => void): Promise<string> => {
      if (!sessionRef.current) {
        throw new Error("Session is not initialized");
      }

      abort();
      const controller = new AbortController();
      abortControllerRef.current = controller;
      setIsGenerating(true);
      setError(null);

      let accumulatedResponse = "";

      try {
        const stream = sessionRef.current.promptStreaming(promptText, {
          signal: controller.signal,
        });

        const reader = stream.getReader();
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          if (value) {
            accumulatedResponse = value;
            onChunk(value);
          }
        }

        if (sessionRef.current) {
          setTokensRemaining(sessionRef.current.tokensLeft);
        }

        return accumulatedResponse;
      } catch (err: unknown) {
        if (err instanceof Error && err.name === "AbortError") {
          return accumulatedResponse;
        }
        const errorMsg = err instanceof Error ? err.message : "Generation failed";
        setError(errorMsg);
        throw err;
      } finally {
        setIsGenerating(false);
        abortControllerRef.current = null;
      }
    },
    [abort]
  );

  const generateText = useCallback(
    async (promptText: string): Promise<string> => {
      if (!sessionRef.current) {
        throw new Error("Session is not initialized");
      }

      abort();
      const controller = new AbortController();
      abortControllerRef.current = controller;
      setIsGenerating(true);
      setError(null);

      try {
        const result = await sessionRef.current.prompt(promptText, {
          signal: controller.signal,
        });
        if (sessionRef.current) {
          setTokensRemaining(sessionRef.current.tokensLeft);
        }
        return result;
      } catch (err: unknown) {
        if (err instanceof Error && err.name === "AbortError") {
          return "";
        }
        const errorMsg = err instanceof Error ? err.message : "Generation failed";
        setError(errorMsg);
        throw err;
      } finally {
        setIsGenerating(false);
        abortControllerRef.current = null;
      }
    },
    [abort]
  );

  return {
    availability,
    downloadProgress,
    isGenerating,
    error,
    generateText,
    streamText,
    abort,
    resetSession: initSession,
    tokensRemaining,
  };
}

Interaktyvus komponentas: <SmartTextArea />

Šis komponentas sujungia nulinės delsos šešėlinio teksto užbaigimą, tono įvertinimą ir būsenos be interneto rodinį. Kai naudotojas trumpam sustoja rašyti, Gemini Nano prognozuoja tolesnį sakinio tęsinį. Paspaudus Tab klavišą, pasiūlymas iškart perkeliamas į laukelį.

TSX
// components/SmartTextArea.tsx
"use client";

import React, { useState, useRef, useEffect, useCallback } from "react";
import { useBrowserAI } from "@/hooks/useBrowserAI";
import { sanitizePromptText } from "@/lib/pii-scrubber";

interface SmartTextAreaProps {
  initialValue?: string;
  placeholder?: string;
  onChange?: (value: string) => void;
  debounceMs?: number;
}

type SentimentTone = "positive" | "constructive" | "neutral" | "urgent" | "analyzing";

export function SmartTextArea({
  initialValue = "",
  placeholder = "Draft your architectural decision record or engineering notes...",
  onChange,
  debounceMs = 280,
}: SmartTextAreaProps) {
  const [text, setText] = useState(initialValue);
  const [suggestion, setSuggestion] = useState("");
  const [tone, setTone] = useState<SentimentTone>("neutral");
  const [isOffline, setIsOffline] = useState(!navigator.onLine);

  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const timerRef = useRef<NodeJS.Timeout | null>(null);

  // Initialize the local completion session with a constrained system prompt
  const {
    availability,
    downloadProgress,
    isGenerating,
    generateText,
    abort,
    tokensRemaining,
  } = useBrowserAI({
    systemPrompt:
      "You are an autocompletion engine for software engineers. Provide a short, direct inline continuation (1 to 8 words) for the user's text. Return ONLY the continuation words. Do not repeat the input.",
    temperature: 0.2,
    topK: 1,
  });

  // Track browser connectivity
  useEffect(() => {
    const handleOnline = () => setIsOffline(false);
    const handleOffline = () => setIsOffline(true);

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);
    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  // Request completion from local model
  const triggerCompletion = useCallback(
    async (currentText: string) => {
      if (availability !== "readily" || currentText.trim().length < 8) {
        setSuggestion("");
        return;
      }

      // Sanitize input to protect sensitive data locally
      const { sanitizedText } = sanitizePromptText(currentText);

      try {
        const rawPrediction = await generateText(
          `Text: "${sanitizedText}"\nContinuation:`
        );

        const cleanPrediction = rawPrediction
          .replace(/^["'\s]+|["'\s]+$/g, "")
          .trim();

        if (cleanPrediction.length > 0) {
          setSuggestion(cleanPrediction);
        } else {
          setSuggestion("");
        }
      } catch {
        setSuggestion("");
      }
    },
    [availability, generateText]
  );

  // Debounced input handler
  const handleInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const newText = e.target.value;
    setText(newText);
    setSuggestion("");
    abort();

    if (onChange) {
      onChange(newText);
    }

    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }

    timerRef.current = setTimeout(() => {
      triggerCompletion(newText);
    }, debounceMs);
  };

  // Keyboard navigation for ghost text acceptance
  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
    if (e.key === "Tab" && suggestion.length > 0) {
      e.preventDefault();
      const mergedText = text.endsWith(" ")
        ? text + suggestion
        : text + " " + suggestion;
      setText(mergedText);
      setSuggestion("");
      if (onChange) onChange(mergedText);
    } else if (e.key === "Escape") {
      setSuggestion("");
      abort();
    }
  };

  return (
    <div className="smart-textarea-container" style={{ position: "relative", width: "100%" }}>
      {/* Header telemetry and indicators */}
      <div
        className="telemetry-bar"
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          marginBottom: "8px",
          fontSize: "12px",
          fontFamily: "monospace",
        }}
      >
        <div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
          <span
            style={{
              display: "inline-flex",
              alignItems: "center",
              gap: "6px",
              color: availability === "readily" ? "#15803d" : "#b45309",
            }}
          >
            <span
              style={{
                width: "8px",
                height: "8px",
                borderRadius: "50%",
                backgroundColor: availability === "readily" ? "#22c55e" : "#f59e0b",
              }}
            />
            {availability === "readily"
              ? "Gemini Nano (Local Engine Active)"
              : availability === "after-download"
              ? `Downloading Weights (${downloadProgress || 0}%)`
              : "Chrome AI Unavailable (Fallback Mode)"}
          </span>

          {tokensRemaining !== null && (
            <span style={{ color: "#64748b" }}>
              Budget: {tokensRemaining} tokens left
            </span>
          )}
        </div>

        <div style={{ display: "flex", gap: "8px" }}>
          {isOffline && (
            <span
              style={{
                backgroundColor: "#fef3c7",
                color: "#92400e",
                padding: "2px 8px",
                borderRadius: "4px",
              }}
            >
              Offline Mode
            </span>
          )}
        </div>
      </div>

      {/* Editor overlay stack */}
      <div style={{ position: "relative", minHeight: "160px" }}>
        {/* Ghost text display layer */}
        <div
          aria-hidden="true"
          style={{
            position: "absolute",
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            padding: "12px",
            fontFamily: "inherit",
            fontSize: "14px",
            lineHeight: "1.5",
            pointerEvents: "none",
            whiteSpace: "pre-wrap",
            wordBreak: "break-word",
            color: "transparent",
            border: "1px solid transparent",
          }}
        >
          <span>{text}</span>
          {suggestion && (
            <span style={{ color: "#94a3b8", opacity: 0.8 }}>
              {text.endsWith(" ") ? "" : " "}
              {suggestion}
            </span>
          )}
        </div>

        {/* User interactive input */}
        <textarea
          ref={textareaRef}
          value={text}
          onChange={handleInput}
          onKeyDown={handleKeyDown}
          placeholder={placeholder}
          aria-label="Smart Content Editor"
          style={{
            width: "100%",
            minHeight: "160px",
            padding: "12px",
            fontSize: "14px",
            lineHeight: "1.5",
            fontFamily: "inherit",
            backgroundColor: "transparent",
            border: "1px solid #cbd5e1",
            borderRadius: "6px",
            resize: "vertical",
            outline: "none",
            boxSizing: "border-box",
          }}
        />
      </div>

      {/* Footer controls and keyboard hints */}
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          marginTop: "6px",
          fontSize: "12px",
          color: "#64748b",
        }}
      >
        <span>
          {suggestion ? "Press [Tab] to accept completion, [Esc] to dismiss" : "Type to see inline local completions"}
        </span>
        {isGenerating && <span>Generating prediction...</span>}
      </div>
    </div>
  );
}
Loading Chrome AI interactive sandbox...

Našumo ir delsos palyginimas

Norėdami įvertinti realų naudotojų patirties pagerėjimą, palyginome Gemini Nano veikimą Chrome su įprastu debesijos API diegimu (Gemini Flash centriniame regione per Next.js Edge maršrutą).

Empirinis delsos ir išteklių palyginimas

Vertinimo kriterijus Chrome Built-in AI (Gemini Nano) Debesijos API per Next.js Edge
Pirmojo žetono laikas (TTFT) 12 ms - 24 ms 420 ms - 980 ms
Tinklo srautas vienai užklausai 0 KB (Jokio tinklo I/O) 1,4 KB - 8,2 KB
PII duomenų rizika Jokios rizikos (Lieka įrenginio atmintyje) Reikalingas TLS siuntimas ir DPA sutartis
Operacinė vieneto kaina 0,00 $ / milijonui užklausų 0,15 0,60- 0,60 / milijonui žetonų
Darbas be interneto Visiškas funkcionalumas Užklausa nepavyksta (HTTP 503 / Tinklo klaida)
Aparatinės įrangos apkrova ~400 MB VRAM / RAM Jokių atminties sąnaudų kliento pusėje

Gamybos iššūkiai ir architektūriniai saugikliai

Modelių vykdymas kliento įrenginyje sukelia iššūkių, kurių nebūna serverio mikropaslaugose.

1. Konteksto lango išnaudojimas ir atminties valymas

Gemini Nano turi ribotą žetonų skaičių (priklausomai nuo įrenginio paprastai 1024–4096 žetonai sesijai). Jei sesija pakartotinai naudojama kelioms ilgoms užklausoms jos neatlaisvinant, session.tokensLeft pasiekia nulį ir tolesni iškvietimai sukelia InvalidStateError.

Sprendimas: Naudokite session.clone() vienkartinėms užduotims kurti ir iškvieskite session.destroy() iškart gavę atsakymą.

TYPESCRIPT
// Pattern: Ephemeral session cloning
async function runIsolatedTask(
  baseSession: AILanguageModelSession,
  taskPrompt: string
): Promise<string> {
  const ephemeralSession = await baseSession.clone();
  try {
    return await ephemeralSession.prompt(taskPrompt);
  } finally {
    ephemeralSession.destroy(); // Free underlying neural runtime resources
  }
}

2. Hibridinė atsarginė architektūra

Ne kiekvieno naudotojo naršyklė palaiko aparatine įranga spartinamus vietinius modelius. Todėl gamybinė architektūra turi taikyti palaipsnį tobulinimą:

  1. 1 lygis (Vietinis): Patikrinti window.LanguageModel (arba window.ai.languageModel). Jei grąžinama "readily", vykdyti vietoje su mažesne nei 20 ms delsa ir be serverio apkrovos.
  2. 2 lygis (Edge atsarginis kelias): Jei capabilities.available === "no", nukreipti užklausą į Next.js Server Action arba Edge Route su debesijos modeliu.
  3. 3 lygis (Paprastasis režimas): Visiškai neturint interneto ir nesant vietinio DI, pereiti prie taisyklių logikos be kritinių klaidų.

Išvados

Skaičiavimų perkėlimas iš tolimų serverių tiesiai į naršyklę pašalina delsos kliūtis interaktyviose sąsajose. Sujungus Chrome Prompt API su patikimu PII filtravimu, apgalvotais React kabliukais ir hibridiniais atsarginiais maršrutais, programuotojai gali kurti greitas, privatumą užtikrinančias ir patikimai veikiančias programas bet kurioje aplinkoje.

Share this article