Naar de inhoud
NLEN
Illustratie: Shadow deployments en dark launching van LLM-API-updates

Shadow deployments en dark launching van LLM-API-updates

Door Ivo Donker — samengesteld met AI-ondersteuning (Claude & Gemini)

Het updaten van een LLM-component in een productie-infrastructuur brengt aanzienlijke risico's met zich mee. Waar traditionele microservices falen met duidelijke HTTP-statuscodes of stack traces, falen taalmodellen vaak stil: een nieuw modelpoint introduceert subtiele hallucinaties, breekt JSON-schema's of reageert met een onvoorspelbare tail-latency. Om deze risico's te mitigeren binnen de kwaliteitsborging van API-omgevingen, kijken we naar geavanceerde testpatronen. Een integraal overzicht van metingen en logging vind je in het artikel over observability en logging voor LLM-toepassingen, waarin de fundamenten van traceerbaarheid worden behandeld.

Traditionele offline evaluatiesets en synthetische benchmarks geven zelden een compleet beeld van de dynamische, vaak rommelige invoer van echte eindgebruikers. Door productieverkeer te spiegelen (shadowing) of nieuwe modellen stilletjes parallel te laten draaien zonder dat de gebruiker de uitvoer ziet (dark launching), testen we wijzigingen tegen levensechte productiedata zonder dat eventuele fouten de gebruikerservaring aantasten.

Het fundamentele verschil tussen shadow deployments en dark launching

Hoewel de termen vaak door elkaar worden gebruikt, lossen shadow deployments en dark launching twee verschillende technische uitdagingen op in een LLM-architectuur.

Bij een shadow deployment (ook wel traffic mirroring genoemd) dupliceert de infrastructuur inkomende HTTP-verzoeken. Het primaire verzoek gaat naar de huidige productieversie van het model (de baseline). Een exacte kopie van het verzoek wordt asynchroon doorgestuurd naar de kandidaatversie (de shadow). Het antwoord van de shadow wordt nooit teruggestuurd naar de gebruiker, maar opgeslagen in een audit- of evaluatiedatabase. Het doel is uitsluitend observatie: hoe presteert de kandidaat qua tokenverbruik, JSON-conformiteit, semantische drift en latentie?

Bij een dark launch draait de nieuwe functionaliteit of het nieuwe model al actief mee in het primaire uitvoeringspad van de applicatie, maar de resulterende data blijft verborgen voor de gebruikersinterface. Denk aan een scenario waarin een nieuw summarization-model de samenvatting genereert en wegschrijft naar de database, terwijl de frontend nog steeds de samenvatting van het oude model toont. Dark launching stelt engineers in staat om de volledige keten inclusief downstream systemen, cache-invalidering en database-schrijfoperaties onder reële belasting te testen.

Eigenschap Shadow Deployment (Traffic Mirroring) Dark Launching
Aanroepende laag Gateway / Proxy niveau (fire-and-forget) Applicatielogica / Feature flag niveau
Impact op downstream systemen Geen (geïsoleerd geëvalueerd) Volledig (databases, caches, afhankelijkheden)
Gebruikersimpact bij falen Nul (fouten worden genegeerd) Laag tot gemiddeld (kan backend-resources belasten)
Doel Regressietesten van modelgedrag en latentie Validatie van end-to-end systeemintegratie

Architectuur van een shadow pipeline in een API-gateway

De meest robuuste plek om shadow traffic in te richten is op de gateway-laag. Hierdoor hoeven individuele microservices geen weet te hebben van de evaluatie-experimenten. Voor een diepgaande analyse over het opzetten van zo'n centrale schakel, zie de gids over zelf een LLM-gateway hosten om routering en failover onder eigen beheer te houden.

In een schaalbare architectuur ontvangt de gateway een request van de client. De gateway stuurt het synchrone request direct door naar de primaire LLM-provider. Tegelijkertijd plaatst een non-blocking dispatcher een kopie van de payload op een interne message broker (zoals Redis Streams, Apache Kafka of RabbitMQ). Een dedicated worker-pool pakt de berichten op en voert de aanroep uit tegen de kandidaat-API. Dit voorkomt dat vertragingen bij de shadow-provider de Time To First Byte (TTFB) van de primaire gebruiker beïnvloeden.

// Voorbeeld: Express / Node.js non-blocking shadow proxy middleware
import { Request, Response, NextFunction } from 'express';
import { Queue } from 'bullmq';

const shadowQueue = new Queue('llm-shadow-evals', {
  connection: { host: 'localhost', port: 6379 }
});

export async function shadowTrafficMiddleware(req: Request, res: Response, next: NextFunction) {
  // Controleer of shadowing actief is voor dit endpoint
  const shadowTarget = req.headers['x-shadow-model'] || process.env.ACTIVE_SHADOW_MODEL;
  
  if (shadowTarget) {
    const shadowPayload = {
      originalUrl: req.originalUrl,
      headers: { ...req.headers, host: undefined },
      body: req.body,
      targetModel: shadowTarget,
      timestamp: Date.now(),
      requestId: req.headers['x-request-id'] || crypto.randomUUID()
    };

    // Plaats asynchroon op de wachtrij zonder op het resultaat te wachten
    shadowQueue.add('evaluate-shadow', shadowPayload, {
      removeOnComplete: true,
      attempts: 1 // Geen onnodige retries bij shadow traffic
    }).catch(err => {
      // Falen van schaduwverkeer mag NOOIT de hoofdthread blokkeren
      console.error('Fout bij enqueue van shadow payload:', err.message);
    });
  }

  next();
}

Omgaan met muterende functies en idempotentie

Een van de grootste gevaren bij het spiegelen van LLM-aanroepen ontstaat wanneer modellen gebruikmaken van function calling of tool use. Als een prompt de instructie bevat om een e-mail te versturen, een order aan te maken of een recordsleutel te muteren, leidt blindelings dupliceren van het verzoek tot dubbele transacties in externe systemen.

Om dit te voorkomen, moeten shadow workers strikt draaien in een read-only sandbox of gebruikmaken van gemockte tool-uitvoeringen. Wanneer het kandidaatmodel een tool-call genereert (zoals verstuur_factuur(klant_id, bedrag)), voert de shadow-omgeving de actie niet daadwerkelijk uit. In plaats daarvan registreert de evaluatielaag dat de toolcall is aangeroepen met specifieke argumenten, waarna een synthetisch succesbericht aan het model wordt teruggekoppeld om de rest van de dialoog te valideren.

Daarnaast is het essentieel om unieke request-identifiers te scheiden. Wie dieper wil duiken in het vermijden van dubbele mutaties en netwerkfouten, leest het artikel over idempotentie bij LLM-API-calls om te begrijpen hoe deduplicatiesleutels functioneren in gedistribueerde systemen.

Evaluatiemetrieken en geautomatiseerde regressiedetectie

Zodra zowel het baseline- als het schaduwmodel antwoord hebben gegeven op hetzelfde productie-inputrecord, start de vergelijkingsfase. Omdat LLM-uitvoer inherent niet-deterministisch is, volstaat een simpele string-vergelijking (zoals een diff) niet. We hanteren vier categorieën van geautomatiseerde evaluatiemetrieken:

1. Syntactische conformiteit: Indien de API gestructureerde JSON vereist, controleert een validator of de uitvoer van het schaduwmodel valideert tegen het vereiste JSON-schema. Een stijging van 0.1% in schema-validatiefouten is een directe blokkade voor promotie naar productie.

2. Latentie en doorvoer: We meten de P50, P95 en P99 latentie en het aantal gegenereerde tokens per seconde. Modellen die op papier goedkoper zijn maar een tweemaal zo hoge P99 latentie vertonen, kunnen SLA-overtredingen veroorzaken.

3. Semantische consistentie: Door embeddings te berekenen van beide antwoorden en de cosinus-overeenkomst te bepalen, detecteren we sterke inhoudelijke afwijkingen. Een lage overeenkomst triggert een nader onderzoek.

4. LLM-as-a-Judge evaluatie: Voor kwalitatieve analyses kan een scheidsrechtermodel (bijvoorbeeld een groter, deterministisch ingesteld model) asynchroon beide antwoorden beoordelen op accuraatheid, feitelijkheid en toon. Om te zien hoe dit past binnen een complete testpiramide, lees je de gids over geautomatiseerd testen van LLM-integraties voor de juiste testopzet.

Voor teams die structureel beleid willen vastleggen rondom niet-deterministische antwoorden en kwaliteitsstandaarden, biedt het dossier over acceptatietests inrichten voor niet-deterministische output concrete methodieken om acceptatiecriteria meetbaar te maken.

# Voorbeeld: Asynchrone evaluatiescript voor semantische drift en schemaconformiteit
import json
import jsonschema
from sentence_transformers import util

def evalueer_shadow_paar(baseline_res: dict, shadow_res: dict, json_schema: dict, embedder) -> dict:
    rapport = {
        "schema_valide": False,
        "token_delta": shadow_res["usage"]["total_tokens"] - baseline_res["usage"]["total_tokens"],
        "latentie_delta_ms": shadow_res["latency_ms"] - baseline_res["latency_ms"],
        "semantische_overeenkomst": 0.0
    }
    
    # 1. Valideer schema
    try:
        shadow_data = json.loads(shadow_res["content"])
        jsonschema.validate(instance=shadow_data, schema=json_schema)
        rapport["schema_valide"] = True
    except (json.JSONDecodeError, jsonschema.ValidationError):
        rapport["schema_valide"] = False

    # 2. Bereken semantische vergelijkingsscore
    emb1 = embedder.encode(baseline_res["content"], convert_to_tensor=True)
    emb2 = embedder.encode(shadow_res["content"], convert_to_tensor=True)
    rapport["semantische_overeenkomst"] = float(util.cos_sim(emb1, emb2)[0][0])
    
    return rapport

Kostenbeheersing en selectieve sampling

Het dupliceren van productieverkeer verdubbelt in theorie de variabele API-kosten, aangezien voor elk inkomend verzoek twee LLM-aanroepen worden uitgevoerd. Bij grootschalige systemen met miljoenen tokens per dag is 100% traffic shadowing financieel niet houdbaar.

De oplossing ligt in dynamische sampling. In plaats van alle requests te spiegelen, configureert de gateway een sampling-percentage (bijvoorbeeld 5% of 10% van het totale volume). Daarnaast kan de sampling gericht worden ingezet op risicovolle verkeersstromen:

Om te voorkomen dat experimentele shadow runs onverwachts budgetten overschrijden, is strikte monitoring noodzakelijk. Zie het overzicht over kosten en budgetten monitoren voor richtlijnen rondom het instellen van harde budgetlimieten en waarschuwingen per API-sleutel.

Regressietesten bij prompt- en modelwijzigingen

Wanneer een provider een modelversie update (bijvoorbeeld van een checkpoint uit juni naar een checkpoint uit augustus), kunnen prompts die maandenlang foutloos functioneerden plotseling falen. Systematische schaduwruns vangen deze regressie vroegtijdig op.

Naast live shadowing is het raadzaam om periodiek regressietests uit te voeren op historische productiedata die tijdens eerdere schaduwsessies is gecureerd. Om te leren hoe je structureel voorkomt dat prompt-optimalisaties op andere domeinen tot kwaliteitsverlies leiden, raadpleeg je het artikel over regressietesten voor prompts om vangrails op te zetten tegen stiekeme kwaliteitsdaling.

Faalmodi, knelpunten en mitigatiestrategieën

Het implementeren van shadow deployments introduceert specifieke faalmodi die een engineer moet onderkennen:

Faalmodus 1: Rate limits bij upstream providers. Door verkeer te verdubbelen, loopt de organisatie het risico tegen provider-quotums (TPM/RPM) aan te lopen, wat de productieaanroepen kan blokkeren. Mitigatie: Gebruik gescheiden API-accounts of specifieke organisatiesleutels met eigen rate limits voor shadow workers.

Faalmodus 2: Geheugenuitputting in wachtrijen. Wanneer het schaduwmodel aanzienlijk trager is dan het productiemodel, loopt de berichtenwachtrij (broker) snel vol, wat leidt tot servercrashes. Mitigatie: Stel een harde max_queue_size in met een drop-oldest of drop-newest beleid (lossy shadowing).

Faalmodus 3: Datalekken in testomgevingen. Productiedata bevat regelmatig persoonsgegevens (PII). Als shadow logs zonder restricties worden opgeslagen voor evaluatie, ontstaat een compliancerisico. Mitigatie: Pas inline pseudonimisering toe op payloads voordat deze naar de evaluatiedatabase worden weggeschreven.

Knelpunt Impact Mitigatiestrategie Trade-off
Provider Rate Limits Productiestoring door 429 statuscodes Aparte API-sleutelpool en strikt gescheiden quota Extra beheer van provideraccounts
Wachtrij-congestie Servergeheugen raakt uitgeput Drop-policy bij overschrijding buffergrens Verlies van een percentage evaluatiedata
Verdubbelde kosten Onverwachte budgetuitputting Sampling op basis van hash van requestId (bv. 5%) Langere tijd nodig om statistische significantie te bereiken
Muterende Toolcalls Dubbele externe acties (e-mail, orders) Sandboxed mock-handlers voor shadow workers Extra mock-logica vereist per tool-definitie

Gefaseerde uitrol: Van shadow naar dark launch en canary

Een robuust releaseproces voor LLM-updates combineert de verschillende technieken in een vast stappenplan:

  1. Fase 1: Offline Evaluatie. De nieuwe prompt of het kandidaatmodel doorloopt een vaste testsuite met gecureerde testgevallen.
  2. Fase 2: Shadow Deployment (10% sampling). Verkeer wordt asynchroon gedupliceerd op gateway-niveau. Er wordt gemeten op schemavalidatie, semantische drift en latentie over minimaal 10.000 requests.
  3. Fase 3: Dark Launch. Het model wordt geïntegreerd in de backend-applicatielogica. Downstream opslag en caching worden geverifieerd zonder frontend-weergave.
  4. Fase 4: Canary Release (5% → 25% → 100%). Een klein percentage gebruikers krijgt daadwerkelijk de uitvoer van het nieuwe model te zien. Observability dashboards monitoren gebruikersinteracties, duimpjes omhoog/omlaag en foutpercentages.
  5. Fase 5: Volledige promotie & Archivering. Het oude modelpunt wordt uitgefaseerd en de shadow pipeline wordt uitgeschakeld.

Door deze systematische aanpak te hanteren, transformeren we LLM-updates van riskante, onvoorspelbare operaties naar gecontroleerde, data-gedreven software-releases.