Most eval suites lie because they ignore data parity staging vs production for evals. If your staging dataset is a cleaned subset of production traffic, your metrics are optimistic and your rollouts surprise you. Achieving data parity staging vs production for evals means treating live requests as the only valid seed for testing.
1. Capture production traffic as the ground truth
Log every inference call in production. Strip nothing at the edge; store the raw prompt, model name, sampling params, and response. Use an append-only topic so you can replay later.
@app.middleware("http")
async def log_traffic(request, call_next):
response = await call_next(request)
if "/v1/complete" in request.url.path:
body = await request.body()
await kafka.send("llm_traffic", {
"ts": time.time(),
"prompt": body.decode(),
"model": request.headers.get("x-model"),
"params": request.headers.get("x-params"),
})
return response
Pitfall: sampling at the proxy hides tail latency and rare inputs. Capture 100% for at least a rolling window.
2. Build a replayable corpus with timestamps
Store records in a columnar store with a strict schema. You need ts, prompt, model, params, and response (if you keep it). This becomes your frozen eval set.
CREATE TABLE llm_traffic (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ NOT NULL,
prompt TEXT NOT NULL,
model TEXT NOT NULL,
params JSONB,
response TEXT
);
Tradeoff: keeping responses inflates storage but enables regression tests on historical outputs. Drop them after 30 days if cost bites.
3. Mask PII without destroying signal
Mask before the data leaves the production account. Use deterministic hashes for user IDs, redact explicit patterns, but keep structural noise.
import re
def mask_pii(text):
text = re.sub(r"\b\d{3}-\d{2}-\d{4}\b", "[SSN]", text)
text = re.sub(r"\b[\w.]+@[\w.]+\b", "[EMAIL]", text)
return text
Over-masking turns prompts into generic templates and breaks data parity staging vs production for evals. Test your mask against a sample to confirm entity variety survives.
4. Sample to match production distribution
Random sampling is not enough. Stratify by model, tenant tier, and query length so staging sees the same shape as production.
SELECT * FROM llm_traffic
WHERE ts > now() - interval '7 days'
TABLESAMPLE BERNOULLI (1)
For finer control, pull equal rows per stratum in Python:
strata = df.groupby(["model", "tenant_tier"])
sample = strata.apply(lambda g: g.sample(min(len(g), 50)))
The goal of data parity staging vs production for evals is distribution equality, not just volume coverage.
5. Enforce model and config parity
Your eval is invalid if staging runs a different checkpoint than production. Pin model versions and temperature exactly. Point both environments at a single OpenAI-compatible endpoint that addresses 240+ models and honors client routing directives—n4n.ai does this, forwarding provider cache-control hints so staging evals hit the same cached weights as production. That removes model drift from the parity equation.
{
"dataset": "prod_replay_2024_06",
"model": "gpt-4o-mini",
"temperature": 0.0,
"metrics": ["exact", "rouge", "latency_p95"]
}
6. Run staging evals against the corpus
Replay the masked corpus through staging with the production config. Measure output deltas and latency.
python eval_runner.py --corpus prod_replay_2024_06 --config eval.json --out report.json
Common pitfall: running evals at off-peak hours hides contention. Schedule replays during production-like load windows or inject synthetic load.
7. Diff results and set parity gates
Compare staging output to the stored production response (or a golden set). Compute semantic similarity, not just string match.
from difflib import SequenceMatcher
score = SequenceMatcher(None, prod_text, stage_text).ratio()
Gate CI on three conditions:
- Median similarity drop < 2%
- p95 latency within 10% of production
- Zero increase in safety violations
If any fails, block the deploy. This is where data parity staging vs production for evals pays off: you catch behavior drift before users do.
8. Common pitfalls and tradeoffs
- Stale corpus: A two-week-old replay misses new user behavior. Refresh daily.
- Legal hold: Some jurisdictions forbid moving production prompts to staging. Use synthetic generation seeded by production stats instead.
- Cost: Replaying 1M prompts through a frontier model is expensive. Subsample by stratum and weight results.
- Cache leakage: If staging hits the same semantic cache as production, you measure cache hits, not model behavior. Disable cache for eval runs.
9. Operationalize the loop
Wire it into the pipeline. A nightly job rebuilds the corpus, masks, samples, and runs the gate. Surface the report in PR comments.
eval_job:
schedule: "0 3 * * *"
steps:
- capture_prod_window
- mask_and_sample
- run_eval_gate
- post_report
Data parity staging vs production for evals is not a one-time project. It is a continuous mirror of reality into your test bed. Ship the mirror first, then trust your evals.