Zurück zum Journal
14 Min. Lesezeit

Zero-Latency On-Device KI in React: Chrome Gemini Nano & Prompt API im Produktiveinsatz

Erstellung von latenzfreien, datenschutzfreundlichen React-Schnittstellen mit den integrierten Gemini Nano APIs von Chrome, window.LanguageModel, lokaler PII-Bereinigung und resilienten Fallbacks.

React.jsTypeScriptIn-Browser LLMsAI ArchitectureGDPR & PII Management
Zero-Latency On-Device KI in React: Chrome Gemini Nano & Prompt API im Produktiveinsatz

Zero-Latency On-Device KI in React: Chrome Gemini Nano & Prompt API im Produktiveinsatz

Jeder Tastenanschlag in einer modernen Webanwendung verlangt sofortiges Feedback. Wenn Entwicklungsteams große Sprachmodelle an Eingabefelder für Autovervollständigung, Tonanpassungen oder Echtzeit-Zusammenfassungen anbinden, stoßen sie auf eine physikalische Hürde: Netzwerklaufzeiten.

Ein Roundtrip vom Browser zu einem Cloud-LLM-Gateway benötigt zwischen 300 ms und 1500 ms bis zum ersten Token (TTFT). Diese Verzögerung ist für ein separates Chat-Fenster akzeptabel, unterbricht jedoch den Schreibfluss in Editoren und Eingabeformularen. Cloud-Inferenz verursacht zudem wiederkehrende Token-Kosten, Kaltstarts, Rate-Limits und Compliance-Risiken, sobald vertrauliche Benutzereingaben den Client verlassen.

Integrierte Browser-KI in Chrome verändert diese Ausgangslage grundlegend, indem Gemini Nano direkt in die Browser-Laufzeitumgebung eingebettet wird. Dieser Artikel beschreibt die Architektur, den Sitzungslebenszyklus, clientseitige Datenhygiene und konkrete React-Integrationsmuster für lokale Modelle in der Produktion.

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)   |
+-------------------------------------------------------------------------+

Das browserinterne Inferenzmodell

Chrome liefert Gemini Nano als lokales neuronales Basismodell aus, das über den Optimization Guide Service des Browsers verwaltet wird. Im Gegensatz zu WebAssembly- oder WebGPU-Portierungen, bei denen Benutzer beim ersten Seitenaufruf 1 GB bis 4 GB an Modellgewichten herunterladen müssen, steuert Chrome die Binärverteilung im Hintergrund über seinen Component Updater.

Die Inferenz läuft in einem isolierten Hilfsprozess ab, getrennt von Skripten des Webinhalts. Der JavaScript-Ausführungskontext interagiert mit dem Modell über strukturierte Interprozesskommunikation (IPC) über die Standard-API window.LanguageModel (und den Übergangs-Namensraum window.ai.languageModel).

Architektonische Eigenschaften

  1. Deterministischer Datenschutz: Roher Prompt-Text verlässt niemals den lokalen Rechner über ein Netzwerk-Socket. In regulierten Branchen wie dem Gesundheits- und Finanzwesen erfüllt lokale Inferenz strenge Richtlinien ohne Datenabfluss.
  2. TTFT unter 20 ms: Da das Modell im lokalen Speicher liegt und geräteeigene Hardwarebeschleuniger (Apple Silicon Metal, Windows DirectML, Vulkan) nutzt, startet die Tokenerzeugung praktisch verzögerungsfrei.
  3. Keine variablen Infrastrukturkosten: Arbeitslasten werden auf dem Client-Chip ausgeführt. Lastspitzen von Hunderttausenden Nutzern verursachen 0 $ Inferenzkosten.
  4. Offline-Fähigkeit: Funktionen bleiben bei instabilen Mobilfunkverbindungen oder vollständigen Netzausfällen uneingeschränkt nutzbar.

Der Lebenszyklus der integrierten Chrome-KI

Die Prompt-API ist kein einfacher zustandsloser Funktionsaufruf. Sie basiert auf einem zustandsbehafteten Sitzungsmodell, das explizite Fähigkeitsprüfungen, Parameterabstimmung und Speichermanagement erfordert.

TYPESCRIPT
// Typdefinitionen für die 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;
    };
  }
}

Drei Verfügbarkeitszustände des Modells

Vor der Initialisierung einer Sitzung fragen Anwendungen window.LanguageModel (oder window.ai.languageModel) ab. Der Browser gibt einen von drei Zuständen zurück:

  • "readily": Die Modell-Binärdatei ist lokal auf der Festplatte zwischengespeichert und sofort einsatzbereit.
  • "after-download": Das Gerät erfüllt die Hardwareanforderungen, aber die Modell-Binärdatei wartet auf den Download über den Hintergrunddienst von Chrome. Anwendungen können den Fortschritt über den monitor-Callback verfolgen.
  • "no": Dem Gerät fehlt die Hardwareunterstützung (unzureichender VRAM/RAM oder nicht unterstützte GPU-Architektur) oder das Feature-Flag ist deaktiviert.

Clientseitige PII-Bereinigungsschicht

Selbst bei Verwendung eines lokalen Modells ohne Netzwerkabfluss verlangen bewährte Sicherheitsstandards, dass sensible Identifikatoren (Kreditkartennummern, Personalausweisnummern, E-Mail-Adressen, Authentifizierungstoken) vor dem Zusammenstellen des Prompts maskiert werden. Dies verhindert ungewollte Übertragungen in geteilten Sitzungsverläufen.

Hier ist ein schlanker Tokenisierungsfilter ohne externe Abhängigkeiten:

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 };
}

Produktionsreifer React-Hook: useBrowserAI

Die manuelle Verwaltung von Modellsitzungen in React-Komponenten führt zu Speicherlecks, wenn Sitzungen beim Unmounten nicht freigegeben werden. Der Hook useBrowserAI steuert den Lebenszyklus, die Validierung der Fähigkeiten, die Aggregation von Streaming-Text und Abbrüche über Signale.

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,
  };
}

Interaktives Komponenten-Highlight: <SmartTextArea />

Die folgende Komponente vereint latenzfreie Ghost-Text-Vervollständigung mit Stimmungsanalyse und einer Offline-Statusanzeige. Wenn Nutzende beim Tippen kurz innehalten, prognostiziert Gemini Nano den weiteren Satzverlauf. Ein Druck auf die Tab-Taste übernimmt den Vorschlag direkt in den Eingabefluss.

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...

Leistungs- und Latenz-Benchmarks

Um messbare Verbesserungen der Benutzererfahrung zu ermitteln, haben wir Gemini Nano in Chrome mit einer typischen Cloud-API-Bereitstellung verglichen (Gemini Flash gehostet in einer zentralen Cloud-Region über eine Next.js Edge Route).

Empirischer Vergleich von Latenz und Ressourcenbedarf

Evaluierungsmetrik Chrome Built-in AI (Gemini Nano) Cloud API über Next.js Edge Gateway
Time-to-First-Token (TTFT) 12 ms - 24 ms 420 ms - 980 ms
Netzwerk-Egress pro Anfrage 0 KB (Kein Netzwerk-I/O) 1,4 KB - 8,2 KB
PII-Sicherheitsrisiko Kein Risiko (Verlässt den Gerätespeicher nicht) Erfordert TLS-Egress und Auftragsverarbeitung
Betriebliche Stückkosten 0,00 $ / Million Anfragen 0,15 0,60- 0,60 / Million Tokens
Offline-Resilienz Voll funktionsfähig Schlägt fehl (HTTP 503 / Netzwerkfehler)
Hardware-Beanspruchung ~400 MB VRAM / RAM Kein clientseitiger Speicherbedarf

Fallstricke in der Produktion und architektonische Leitplanken

Der Betrieb von Basismodellen auf Client-Hardware bringt Herausforderungen mit sich, die bei serverseitigen Microservices nicht existieren.

1. Erschöpfung des Kontextfensters und Speicherbereinigung

Gemini Nano verfügt über ein begrenztes Token-Budget (typischerweise 1024 bis 4096 Tokens pro Sitzung je nach Geräteklasse). Wenn eine Sitzung über mehrere lange Eingaben hinweg ohne Bereinigung wiederverwendet wird, sinkt session.tokensLeft auf null und nachfolgende Aufrufe werfen einen InvalidStateError.

Lösungsansatz: Nutzen Sie session.clone(), um flüchtige Arbeitssitzungen für Einmalaufgaben zu erzeugen, und rufen Sie nach Erhalt des Ergebnisses sofort session.destroy() auf.

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. Die hybride Fallback-Architektur

Nicht jede Person nutzt einen Browser mit aktivierter hardwarebeschleunigter lokaler Modellunterstützung. Eine produktive Architektur muss daher progressive Verbesserung umsetzen:

  1. Stufe 1 (Lokal): Abfrage von window.LanguageModel (oder window.ai.languageModel). Falls "readily", lokale Ausführung mit einer Latenz unter 20 ms und ohne Serverlast.
  2. Stufe 2 (Edge Fallback): Falls capabilities.available === "no", Weiterleitung an eine Next.js Server Action oder Edge Route mit einem Cloud-Modell.
  3. Stufe 3 (Stufenweise Reduzierung): Bei kompletter Offline-Nutzung ohne lokale KI-Unterstützung Rückfall auf regelbasierte Heuristiken ohne fatale Laufzeitfehler.

Fazit

Die Verlagerung der Inferenz aus entfernten Rechenzentren direkt in den Browser beseitigt Latenzengpässe bei interaktiven Oberflächen. Durch die Kombination der Prompt-API von Chrome mit clientseitiger PII-Filterung, zustandsbehafteten React-Hooks und hybriden Edge-Fallbacks entstehen reaktionsschnelle, datenschutzkonforme Anwendungen für den weltweiten Einsatz.

Share this article