Un model sau un prompt nou poate avea scoruri bune pe setul de evaluare și totuși să se comporte diferit pe datele reale. Shadow deployment (dark launch) trimite cererile reale și către varianta candidată, dar utilizatorul vede doar răspunsul variantei curente. Răspunsurile candidatului se salvează și se compară după calitate, cost și latență.
După faza shadow urmează de obicei o lansare canary (un procent mic de utilizatori primește varianta nouă, procent mărit treptat) sau un test A/B (două variante pe grupuri separate, comparate pe o metrică stabilită dinainte, de exemplu rata de feedback pozitiv). Langfuse, de exemplu, permite versionarea prompturilor și compararea metricilor pe versiuni.
Traficul shadow dublează costul apelurilor pentru porțiunea testată și nu trebuie să execute acțiuni cu efecte (unelte care scriu date, trimit emailuri sau fac plăți). Datele trimise unui furnizor nou trebuie să respecte aceleași reguli de confidențialitate ca în producție.
Concepte cheie și caracteristici
| Concept / Parametru | Descriere practică |
|---|---|
Trafic shadow |
Copie a cererii trimisă candidatului, fără impact asupra răspunsului către utilizator. |
Canary |
Rutarea unui procent mic de trafic real către varianta nouă, crescut treptat dacă metricile rămân bune. |
A/B test |
Două variante pe grupuri de utilizatori separate, cu metrică și durată stabilite înainte de test. |
Comparația automată |
Evaluatori LLM-as-a-judge, metrici de cost și latență, rate de refuz sau de eroare pe aceleași cereri. |
Unelte cu efecte |
În modul shadow, apelurile de unelte care modifică date trebuie dezactivate sau simulate. |
Exemple practice și scenarii reale
Exemplul 1: Trafic shadow cu FastAPI
BackgroundTasks rulează sarcina după ce răspunsul a fost trimis, deci cererea către candidat nu întârzie utilizatorul. salveaza_comparatie() este funcția ta de stocare.
import httpx
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
async def apel_candidat(cerere: dict, raspuns_curent: dict):
async with httpx.AsyncClient(timeout=120) as client:
r = await client.post("http://model-candidat/v1/chat/completions", json=cerere)
salveaza_comparatie(cerere, raspuns_curent, r.json(), eticheta="shadow-v2")
@app.post("/v1/chat/completions")
async def chat(cerere: dict, bg: BackgroundTasks):
async with httpx.AsyncClient(timeout=120) as client:
r = await client.post("http://model-curent/v1/chat/completions", json=cerere)
raspuns = r.json()
bg.add_task(apel_candidat, cerere, raspuns)
return raspuns
Exemplul 2: Canary prin ponderi în LiteLLM
Două implementări în același grup; cu strategia implicită simple-shuffle, parametrul weight din litellm_params stabilește proporția de trafic (aici aproximativ 90% / 10%).
model_list:
- model_name: asistent
litellm_params:
model: anthropic/claude-sonnet-5
weight: 9
- model_name: asistent
litellm_params:
model: anthropic/claude-sonnet-5-5
weight: 1
Greșeli frecvente și capcane
- Dublezi în shadow apelurile de unelte care scriu date, trimit emailuri sau fac tranzacții.
- Lași traficul shadow activ fără limită de timp și fără să urmărești costul dublat.
- Compari variantele pe cereri diferite. Comparația are sens doar pe aceleași cereri sau pe grupuri alese aleator.