LLMOps: eval harness, drift detection e canary de prompts
- ⬜🚀 LLM APIs em Produção: streaming, structured output, batch e cache(Engenharia AI-Native)
- ⬜📊 Avaliando RAG: recall@k, nDCG e LLM-as-judge(Engenharia AI-Native)
Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.
LLMOps é o que separa "demo de IA" de "sistema AI-native em produção". Este módulo fecha a trilha juntando os ingredientes operacionais: eval harness bloqueando deploy ruim, canary de prompts, drift detection (modelo, dado, RAG), cost attribution e SLO de qualidade. Como saber que o pipeline continua entregando.
LLMOps ≠ MLOps: o que muda
| Dimensão | MLOps clássico | LLMOps |
|---|---|---|
| Artefato versionado | Pesos do modelo | Prompts, tools, RAG config, eval sets |
| Quem treina | Você | Provider (Anthropic/OpenAI/Google) |
| Origem de drift | Dado ou pipeline | Provider muda snapshot, dado muda, prompt muda |
| Eval | Test set estático | Eval harness com LLM-as-judge + métricas online |
| Custo | Treino (capex) + inferência | Inferência variável + cache + batch |
| Rollback | Voltar pesos antigos | Voltar prompt + pin de snapshot |
Arquitetura de plataforma LLMOps
┌────────────────────────────────────────────────────┐
│ PROMPT / CONFIG REGISTRY │
│ Git + versionado: system, tools, rag_version │
└────────────────────┬───────────────────────────────┘
│ referência por hash
┌────────────────────▼───────────────────────────────┐
│ EVAL HARNESS (CI) │
│ golden set (100-500) · retrieval + gen metrics │
│ bloqueia PR se regressão > threshold │
└────────────────────┬───────────────────────────────┘
│ passa → deploy
┌────────────────────▼───────────────────────────────┐
│ CANARY ROUTER │
│ 1% → 10% → 50% → 100% rollback automático │
└────────────────────┬───────────────────────────────┘
│
┌────────────────────▼───────────────────────────────┐
│ OBSERVABILITY │
│ traces (Langfuse/LangSmith) · metrics (p50/p95, │
│ tokens, cost, cache ratio) · online signals │
└────────────────────┬───────────────────────────────┘
│ feedback → golden set
┌────────────────────▼───────────────────────────────┐
│ DRIFT MONITORS │
│ modelo · distribuição de queries · RAG staleness │
└────────────────────────────────────────────────────┘
Prompt registry: versionar prompts como código
Prompts devem viver em repo, revisados em PR, versionados por hash. Nunca edite prompt direto em dashboard "sem deploy" — reproduzibilidade é perdida.
# Estrutura simples
# prompts/
# extract_ticket/
# v3.md ← prompt em markdown com frontmatter YAML
# v3.json ← schema de input/output
# v3.eval.jsonl ← golden set opcional específico deste prompt
from pathlib import Path
import hashlib, yaml, json
class Prompt:
def __init__(self, path: Path):
text = path.read_text()
if text.startswith("---"):
meta_block, body = text.split("---", 2)[1:]
self.meta = yaml.safe_load(meta_block)
else:
self.meta, body = {}, text
self.body = body.strip()
self.hash = hashlib.sha256(self.body.encode()).hexdigest()[:12]
def format(self, **kwargs) -> str:
return self.body.format(**kwargs)
# Uso
p = Prompt(Path("prompts/extract_ticket/v3.md"))
prompt_text = p.format(ticket=ticket_text)
# Log: prompt_hash=p.hash, version=p.meta.get("version"), model=p.meta.get("model")Eval harness em CI: bloqueie PR ruim
Coberto no módulo de RAG evaluation. Princípio operacional: golden set ≥ 100 itens, baseline committed no repo, CI compara current vs baseline, falha se regressão > threshold.
# promptfoo é uma das opções prontas — config declarativa
prompts: [file://prompts/extract_ticket/v3.md]
providers:
- id: anthropic:messages:claude-sonnet-4-6
config: { temperature: 0 }
tests:
- description: ticket simples
vars: { ticket: "Meu cartão não passou na renovação" }
assert:
- type: javascript
value: output.includes("billing") && output.includes("card_declined")
- type: latency
threshold: 3000
- type: cost
threshold: 0.01
defaultTest:
assert:
- type: llm-rubric
value: A resposta deve ser JSON válido com campos category, urgency (1-5), summary.
provider: openai:chat:gpt-4o-mini| Ferramenta | Forte em | Fraco em |
|---|---|---|
| promptfoo | Config declarativa, fácil CI, múltiplos providers | Golden set grande vira YAML gigante |
| LangSmith (LangChain) | Traces + datasets integrados; UI rica | Ecosistema LangChain; pricing |
| Langfuse (open-source) | Self-host, traces, datasets, scores | Menos batteries-included que LangSmith |
| RAGAS | Métricas RAG canônicas em código | Menos infra de dataset e CI |
| Custom (pytest + jsonl) | Máximo controle | Você reimplementa tudo |
Canary de prompt: rollout gradual e automático
Novo prompt entra em produção atendendo a uma fração de tráfego. Comparamos métricas com grupo de controle (prompt atual). Se degradação, rollback. Se OK, subimos o percentual.
# Rota simples com sticky assignment por user_id
import hashlib
def canary_prompt(user_id: str, percent: int, control_hash: str, canary_hash: str) -> str:
bucket = int(hashlib.sha256(user_id.encode()).hexdigest(), 16) % 100
return canary_hash if bucket < percent else control_hash
# Ao chamar LLM, log:
# variant = "control" | "canary"
# quality_score (estimado via eval online ou modelo judge)
# cost_usd
# latency_ms
# Regra de promoção (pseudo)
# se canary_sample > 1000 E
# quality_delta > -2pp E
# cost_delta < +15% E
# latency_p95_delta < +200ms:
# promover percent +20 até 100
# senão:
# rollback para 0📋 Mudança de system prompt em endpoint de alto tráfego (customer support)
Mudança parece inofensiva mas pode quebrar edge cases invisíveis ao eval offline (ex: novas intents). Canary expõe gradualmente e rollback em minutos protege receita.
Alt: Deploy direto após eval offline — ok só em endpoints de baixo risco com eval forte
Alt: A/B 50/50 fixo — experimentação estatística; não é o mesmo que safety canary
Drift detection: o sinal que aparece antes do ticket
| Fonte de drift | Sensor | Alert |
|---|---|---|
| Modelo (provider mudou) | Pin de snapshot (não usar "latest"); re-eval semanal contra golden | Diff de quality vs baseline > 3pp |
| Query distribution | Distância (KL/PSI) entre vetores/tópicos de queries hoje vs semana passada | Distância acima de threshold |
| RAG staleness | Data média dos chunks usados; hit ratio em novas queries | Hit ratio caindo ou data média muito antiga |
| Tool drift | Taxa de tool_use errada ou validation error | Aumento súbito de tool errors |
| Usuário refina muito | Taxa de follow-up/re-prompt em conversa | Sobe = qualidade caiu |
| Custo por task | Tokens médios/query | Jump sugere loops ou context growth |
# Exemplo simples de drift por distribuição de tópicos (PSI)
import numpy as np
def psi(expected: np.ndarray, actual: np.ndarray, eps=1e-6) -> float:
expected = np.clip(expected, eps, None); expected /= expected.sum()
actual = np.clip(actual, eps, None); actual /= actual.sum()
return float(np.sum((actual - expected) * np.log(actual / expected)))
# Usa topic distribution (cluster de embeddings) em janela móvel
# PSI < 0.1 estável, 0.1-0.25 atenção, > 0.25 drift significativoCost attribution e FinOps de LLM
| Dimensão | Tag / label | Pra que serve |
|---|---|---|
| endpoint / rota | endpoint=search_rag | Identificar endpoint caro |
| feature / produto | feature=summarize_monthly | Quanto cada feature custa |
| tenant / cliente | tenant=acme (se multi-tenant) | Chargeback ou ROI por cliente |
| model | model=sonnet-4-6 | Impacto de mudança de modelo |
| prompt_hash | prompt_hash=ab12cd34 | Correlacionar custo com versão de prompt |
| variant | variant=canary | Comparar canary vs control |
SLO de qualidade: o que prometer
SLO de LLM combina três famílias de métricas: qualidade, performance, custo. Cada uma com objetivo e janela clara. Exemplo para um assistente de suporte:
- Qualidade: faithfulness ≥ 0.90 (LLM-as-judge, amostra diária de 50 conversas) em janela de 30 dias.
- Qualidade online: thumbs-up rate ≥ 75% nas respostas avaliadas pelo usuário.
- Latência: TTFT p95 ≤ 1200ms; total p95 ≤ 8s.
- Custo: USD por conversa resolvida ≤ 0.05 (mediana mensal).
- Disponibilidade: ≥ 99.5% (provider fail + retry transparente).
Incident playbook: o que fazer quando cai
| Sintoma | Primeira ação | Investigação |
|---|---|---|
| Quality score caiu em dashboard | Rollback do último prompt release | Diff de prompt, snapshot do modelo, composição de queries |
| Custo pulou 2× | Verificar token/query por endpoint | Loops? Context growth? Cache invalidado? |
| Latência estourou | Checar provider status; fallback para outro modelo/provider | Rate limit? Upstream? Streaming quebrou? |
| Usuário reporta resposta errada | Pegar trace pelo request_id | Reprovar em golden set; adicionar caso ao set |
| 429 em massa | Ligar rate limiter local; considerar batch para jobs não-críticos | Subir quota no provider |
Perguntas típicas
❓ Preciso mesmo pinar snapshot de modelo?
❓ Quando vale investir em plataforma LLMOps própria?
❓ LLM-as-judge em produção é confiável para alertar?
❓ Como convenço o time a investir em eval harness antes de feature nova?
Quiz rápido
4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito