Prompts são código. Você não muda código de produção sem teste — não mude prompt sem eval. Aqui você aprende a tratar prompts como artefatos versionáveis: template, hash, golden set, comparação A/B. É a sementeira da disciplina que se aprofunda em T6.
🧬 Prompt-as-code: o ciclo de vida
Trate o prompt exatamente como você trata código de produção: arquivo versionado, PR com diff, CI que roda eval, deploy só com aprovação.
- •Edita: novo arquivo
prompts/classify_v1.2.0.j2. - •Testa:
pytest evals/classify/roda contra golden set. - •Compara: diff métrica vs. versão anterior em PR.
- •Deploy: merge só se métrica não regrediu (≥0 delta) E PR aprovado.
📊 Por que isso importa: regressão silenciosa é o normal
- 30%+ dos casos quebram em mudanças 'pequenas' não-validadas (estudo interno comum).
- Modelo do provedor é atualizado sem aviso — eval contínuo captura.
- Prompt drift: ao longo de meses, prompts viram colcha de retalhos sem ninguém perceber.
💡 Comece pequeno
Não precisa de Weights & Biases dia 1. prompts/v1.0.0.txt + 20 exemplos em golden.jsonl + pytest test_prompt.py já estabelece a disciplina.
📦 Prompt como código: template + variáveis
Separar dado de prompt
Templates separam estrutura do prompt (versionado em git) de dados que entram (variáveis em runtime). Jinja2 é o padrão Python; mustache é portável; f-strings funcionam para casos simples. O efeito é grande: revisores leem o prompt isoladamente em PR, e o mesmo arquivo serve para múltiplos chamadores.
# prompts/classify_v1.0.0.j2
Você classifica sentimento. Saída: positivo, negativo ou neutro.
<exemplos>
{% for ex in exemplos %}
<ex><in>{{ ex.in }}</in><out>{{ ex.out }}</out></ex>
{% endfor %}
</exemplos>
Texto: {{ texto }}
📑 Resumo navegável
🔢 Versionamento: SemVer aplicado a prompts
v1.0.0 do prompt
SemVer aplicado a prompts: v{major}.{minor}.{patch}. Major: muda formato de saída ou persona (quebra cliente). Minor: capability nova compatível. Patch: typo, clareza. Hash sha256 do template + versão são gravados no run manifest do harness — auditável.
| Mudança | Bump | Por quê |
|---|---|---|
| Adicionei vírgula em 'Olá, mundo' | patch (v1.0.1) | estética; comportamento idêntico |
| Adicionei classe 'misto' às opções | minor (v1.1.0) | novo output válido; antigos casos seguem |
| Mudei saída de string para JSON | major (v2.0.0) | quebra qualquer parser que esperava string |
📑 Resumo navegável
🎯 Golden set: o conjunto de validação
20-50 exemplos canônicos
Golden set é seu teste automatizado. Comece com 30 exemplos cobrindo: 60% típicos, 20% edge cases, 20% adversariais. Versionado em evals/v1/datasets/ com hash sha256. O custo de manter é baixo se você adiciona casos quando descobre bugs em produção.
{"id": "001", "in": "Adorei!", "out": "positivo", "tag": "tipico"}
{"id": "002", "in": "Que lixo.", "out": "negativo", "tag": "tipico"}
{"id": "003", "in": "Eh, mais ou menos.", "out": "neutro", "tag": "tipico"}
{"id": "020", "in": "Tá uma droga... brincadeira!", "out": "positivo", "tag": "adversarial"}
{"id": "021", "in": "", "out": "neutro", "tag": "edge"}
📑 Resumo navegável
⚖️ Eval primer: medir antes de mergear
A regra que enraíza em T6
A regra é simples: toda mudança em prompt entra com mini-eval contra o golden set. PR que muda prompt sem rodar eval é equivalente a PR que muda código sem rodar testes. Métrica primária definida; se regredir, não merge. Eval roda em CI fast (mockado) e scheduled (real).
import json, pytest
from meu_app.classify import classificar
def test_golden():
examples = [json.loads(l) for l in open('evals/v1/datasets/classify-v1.jsonl')]
correct = sum(1 for e in examples if classificar(e['in']) == e['out'])
accuracy = correct / len(examples)
assert accuracy >= 0.85, f'regression: {accuracy:.2%} < baseline 85%'
📑 Resumo navegável
📊 Métricas: exact match, BLEU, LLM-as-judge
Escolha pela tarefa
Não existe métrica universal. Exact match para classificação fechada. BLEU/ROUGE para sumarização e tradução. LLM-as-judge para qualidade subjetiva (groundedness, helpfulness) — com cuidado de calibração contra humanos (κ ≥0.6, ver T6.1). Para cada prompt, escolha 1 primária + 1-2 secundárias.
| Tarefa | Primária | Secundária |
|---|---|---|
| Classificação binária/multi-classe | Accuracy/F1 | Custo médio |
| Extração de entidades | F1 por tipo de entidade | Tempo p95 |
| Sumarização | ROUGE-L | LLM-as-judge fluência |
| RAG QA | Groundedness (judge) | Citation accuracy |
| Geração criativa | LLM-as-judge | Diversidade |
📑 Resumo navegável
🔁 Iterativa, não one-shot: o ciclo do prompt
Hipótese → eval → diff
Prompt engineering não é arte intuitiva — é ciência empírica. Ciclo: hipótese → variant → eval → diff → decide. Cada iteração é um PR pequeno com 1 mudança e métricas medidas. Em 3-5 iterações você normalmente atinge o teto da arquitetura escolhida; para subir mais, troca de modelo ou repensa retrieval.
ciclo do prompt
- ▸Hipótese: 'adicionar exemplo adversarial em few-shot melhora F1 em casos com ironia'.
- ▸Variant: v1.1.0 com 1 exemplo extra.
- ▸Eval: roda v1.0.0 e v1.1.0 contra mesmo golden, mesma seed.
- ▸Diff: F1 macro de 0.78 → 0.83; custo +5%.
- ▸Decide: mergea — ganho é maior que custo.
📑 Resumo navegável
📑 Resumo navegável dos tópicos
1 📦 Prompt como código: template + variáveis — Separar dado de prompt
2 🔢 Versionamento: SemVer aplicado a prompts — v1.0.0 do prompt
3 🎯 Golden set: o conjunto de validação — 20-50 exemplos canônicos
4 ⚖️ Eval primer: medir antes de mergear — A regra que enraíza em T6
5 📊 Métricas: exact match, BLEU, LLM-as-judge — Escolha pela tarefa
6 🔁 Iterativa, não one-shot: o ciclo do prompt — Hipótese → eval → diff
✓ O que FAZER
- ✓Versionar prompts em arquivos separados
- ✓Rodar mini-eval em toda mudança de prompt
- ✓Documentar o diff e métricas no PR
- ✓Pinar versão do modelo no eval
✗ O que NÃO fazer
- ✗Hardcoded como string em código Python
- ✗'Eu testei manualmente em 2 casos'
- ✗Mergear com 'melhora performance' sem números
- ✗Eval que vira ruído porque o modelo muda
🚫 Quando NÃO usar
- •Em prototipagem inicial (primeiras horas): vá rápido, depois codifique a disciplina.
- •Para prompts triviais usados 1 vez: 'resumir este texto' não precisa de SemVer.
- •Quando golden set ainda não existe: defina-o antes de virar disciplina obrigatória.
💻 Exemplo de código
# prompts/classify_v1.0.0.j2
# (template Jinja2 — versionado em git)
from jinja2 import Template
from fec_sdk import Message, MessageRole
from fec_sdk.adapters import get_adapter
TEMPLATE = Template(open("prompts/classify_v1.0.0.j2").read())
def classificar(texto: str, modelo: str = "mock") -> str:
prompt = TEMPLATE.render(texto=texto)
client = get_adapter(modelo, model="mock-v1")
resp = client.chat([
Message(role=MessageRole.SYSTEM, content=prompt),
Message(role=MessageRole.USER, content=texto),
])
return resp.content.strip()
# tests/test_classify.py
def test_golden():
golden = json.load(open("evals/classify/golden_v1.jsonl"))
correct = sum(1 for ex in golden if classificar(ex["in"]) == ex["out"])
assert correct / len(golden) >= 0.85 # baseline pinado
🏋️ Exercício hands-on
Você recebe um prompt v1.0.0 + golden set de 20 exemplos. Modifique para v1.1.0 melhorando ≥1 caso sem regredir nenhum. Diff documentado. Em exercicios/modulo-2-2/.
📚 Bibliografia
- Jinja2 docs — Template engine de referência
- Anthropic (2024) — Prompt evaluation best practices
- Reimers & Gurevych (2019) — Sentence-BERT (para similaridade)
- Lin (2004) — ROUGE: Recall-oriented eval
🎯 Resumo do Módulo
- ✓Prompts são código — versione em arquivo separado.
- ✓SemVer aplicado: major/minor/patch para prompts.
- ✓Golden set de 20-50 exemplos é o teste automatizado.
- ✓Mini-eval em toda mudança — antes de mergear.
- ✓Eval primer aqui é semente do que aprofunda em T6.1.
Próximo Módulo:
T3 — RAG e Recuperação