Blog

Versionar prompt como código: rollout, rollback e teste

O prompt é o código mais crítico de um sistema com LLM e costuma ser o menos versionado. Uma string enorme escondida no meio de um arquivo, editada direto em produção porque "é só texto", trocada por alguém que queria melhorar uma resposta e piorou dez outras sem ninguém perceber. Quando a qualidade cai, não há como saber o que mudou, quando mudou nem como voltar ao que funcionava, porque a versão anterior não existe em lugar nenhum: foi sobrescrita. O prompt merece o mesmo rigor que o resto do código: um identificador de versão imutável, um histórico do que cada versão era, um rollout que expõe a mudança a uma fatia do tráfego antes de todo mundo, um rollback que volta em segundos e uma suíte de avaliação que barra a promoção de uma versão pior. Este artigo mostra como tratar o prompt como código de verdade, com registro versionado, manifesto de tráfego, rollout gradual, rollback sem redeploy e o gate de eval que impede a regressão de chegar ao cliente.

2026-07-23 / IA Aplicada / 13 min

01

Editar o prompt em produção é deploy sem versão

O prompt escapa do controle de versão por um motivo cultural: ele parece configuração, não código. É texto, cabe numa variável de ambiente, num campo de banco, num painel de administração, e a tentação de editar direto por ali é grande porque não exige build nem deploy. O problema é que essa edição é um deploy, o mais arriscado de todos, feito sem revisão, sem histórico e sem rollback. Quando alguém ajusta uma frase para melhorar um caso específico, essa frase muda o comportamento do modelo em todos os casos, e o efeito colateral só aparece dias depois, na forma de uma métrica de qualidade que caiu sem que nenhum commit explique por quê. A pergunta "o que mudou entre ontem e hoje" não tem resposta, porque a versão de ontem foi apagada no ato de salvar a de hoje.

O prompt tem todas as propriedades que exigem versionamento: ele muda o comportamento observável do sistema, o efeito de uma mudança é difícil de prever, e voltar atrás precisa ser rápido quando algo dá errado. Tratar o prompt como configuração ignora as duas primeiras propriedades e impossibilita a terceira. A inversão é encará-lo como o que ele é, um artefato de comportamento, e dar a ele o mesmo aparato do código: cada versão ganha um identificador imutável, nunca é editada no lugar, e a troca de qual versão está ativa é uma operação explícita e reversível, não uma edição de campo. O texto continua fácil de mudar, mas cada mudança vira uma versão nova rastreável em vez de uma sobrescrita silenciosa.

Prompt comoComo mudaO que falta quando quebra
Configuração editávelSobrescreve o texto no painel ou no bancoHistórico, revisão e a versão anterior para voltar
Constante no códigoVai junto no deploy, mas sem rollout parcialTestar numa fatia antes de todos e reverter sem redeploy
Artefato versionadoCria versão nova com id imutável; ponteiro escolhe a ativaNada: histórico, rollout gradual e rollback ficam de graça

02

Versão imutável: o prompt tem um id, não um lugar

A base de tudo é a imutabilidade: uma versão de prompt, uma vez criada, nunca muda. Não se edita a versão 3, cria-se a versão 4. Cada versão carrega o texto completo, o modelo e os parâmetros com que foi pensada, e um identificador que a acompanha por toda a vida, nos logs, nas métricas e no rollout. Esse identificador precisa ser derivado do conteúdo, não um contador que alguém incrementa à mão: um hash do texto mais os parâmetros garante que duas versões idênticas colidem no mesmo id e que qualquer alteração, por menor que seja, produz um id diferente. Assim o id vira uma prova de qual texto exato rodou, e uma resposta salva com o id da versão 4 pode ser reproduzida byte a byte anos depois, porque a versão 4 nunca foi tocada.

Com a versão imutável e identificada pelo conteúdo, o log de cada chamada ao modelo passa a registrar não só a entrada e a saída, mas o id da versão de prompt que a produziu. Essa amarração é o que fecha o ciclo de diagnóstico: quando uma resposta ruim aparece, o id no log diz exatamente qual texto de prompt a gerou, e comparar dois ids diz se a diferença de qualidade veio de uma troca de prompt ou de outra coisa. Sem o id no log, a mesma investigação vira arqueologia: adivinhar qual versão estava ativa naquele momento a partir de timestamps e memória de quem mexeu. O id transforma "acho que foi quando mudamos o prompt" numa consulta determinística.

// registry/version.js
// Uma versao de prompt e IMUTAVEL e identificada pelo conteudo.
// O id e um hash do texto + parametros: mesma versao, mesmo id;
// qualquer mudanca produz um id novo. Nunca se edita no lugar.

import { createHash } from 'node:crypto';

export function makePromptVersion({ template, model, params }) {
  const payload = JSON.stringify({ template, model, params });
  const id = createHash('sha256').update(payload).digest('hex').slice(0, 12);
  return Object.freeze({ id, template, model, params });
}

// O registro guarda todas as versoes por id. Adicionar nunca sobrescreve.
export function createRegistry() {
  const byId = new Map();
  return {
    add(version) {
      // Id derivado do conteudo: reinserir a mesma versao e no-op seguro.
      if (!byId.has(version.id)) byId.set(version.id, version);
      return version.id;
    },
    get(id) {
      const v = byId.get(id);
      if (!v) throw new Error('versao de prompt desconhecida: ' + id);
      return v;
    },
  };
}

03

O manifesto de tráfego separa qual versão de quem a recebe

Ter versões imutáveis resolve o histórico, mas não diz qual delas o cliente recebe agora. Essa decisão vive num artefato separado, o manifesto de tráfego: um mapa pequeno e legível que declara, para cada prompt, qual versão é a estável, qual é a candidata e que fatia do tráfego a candidata atende. O manifesto é a única coisa que muda quando se faz um rollout ou um rollback; as versões em si ficam paradas no registro. Essa separação é o que permite trocar de comportamento sem tocar no código: promover a candidata a estável é editar um campo do manifesto, e voltar é editar de novo. O texto do prompt e a decisão de quem o recebe deixam de ser a mesma coisa, e cada um muda no seu ritmo.

A fatia de tráfego precisa ser determinística por usuário, não aleatória por requisição. Se o sorteio fosse por chamada, o mesmo cliente cairia na versão nova numa mensagem e na antiga na seguinte, e a conversa ficaria esquizofrênica, mudando de tom e de regras no meio. A âncora certa é um identificador estável do usuário ou da conversa, passado por um hash que devolve um número entre zero e cem; se esse número cai abaixo da porcentagem da candidata, aquele usuário vê a versão nova de forma consistente em toda a sessão. O mesmo mecanismo dá o controle fino do rollout: dez por cento significa que só os usuários cujo hash cai na primeira décima parte veem a candidata, e subir para cinquenta é mexer num número, não redistribuir nada.

// registry/manifest.js
// O manifesto declara, por prompt, a versao estavel, a candidata e a
// fatia da candidata. Trocar de comportamento e editar este mapa,
// nunca o texto de uma versao ja publicada.

import { createHash } from 'node:crypto';

// Ancora deterministica: mesmo usuario cai sempre na mesma versao.
function bucketOf(userId) {
  const h = createHash('sha256').update(String(userId)).digest();
  return h.readUInt16BE(0) % 100; // 0..99
}

export function resolveVersion(manifest, promptName, userId) {
  const entry = manifest[promptName];
  if (!entry) throw new Error('prompt sem manifesto: ' + promptName);

  // Sem candidata, todo mundo recebe a estavel.
  if (!entry.candidate || entry.rolloutPct <= 0) return entry.stable;

  // A fatia e por usuario, nao por requisicao: a conversa nao troca
  // de versao no meio.
  return bucketOf(userId) < entry.rolloutPct ? entry.candidate : entry.stable;
}

04

Rollout gradual: subir a versão para poucos antes de todos

Um prompt novo bom no laboratório pode ser ruim em produção por motivos que o teste não pegou: uma frase que induz o modelo a ser prolixo, uma instrução que conflita com um caso raro mas frequente o bastante para importar, um tom que soa estranho no canal real. O rollout gradual existe para que esse erro atinja poucos usuários antes de atingir todos. A candidata começa em uma fatia pequena, cinco ou dez por cento, e as métricas dessa fatia são comparadas com as da estável no mesmo período: se a candidata mantém ou melhora a qualidade sem piorar custo e latência, a fatia sobe; se degrada, para. A promoção é uma escada de porcentagens vigiada por métrica, não um salto de zero a cem por decreto.

O que torna o rollout confiável é comparar as duas versões na mesma janela de tempo, não a candidata de hoje contra a estável da semana passada. Tráfego muda ao longo do dia, o tipo de pergunta muda com o contexto, e uma comparação entre períodos diferentes confunde a mudança de prompt com a mudança de tráfego. Rodando estável e candidata lado a lado, cada uma na sua fatia, o mesmo perfil de perguntas passa pelas duas ao mesmo tempo, e a diferença nas métricas isola o efeito do prompt. As métricas que importam são as mesmas de qualquer mudança em sistema com LLM: sinais de qualidade da resposta, custo por chamada, latência e taxa de escalonamento para humano, cada uma quebrada por versão.

Rollout gradual comparando na mesma janela

  manifesto: stable=v3  candidate=v4  rollout=10%
       |
  trafego ---+---> 90% -> v3 (estavel)  --> metricas v3
             |
             +---> 10% -> v4 (candidata) --> metricas v4
                          (por hash do usuario, estavel na sessao)

  compara v3 x v4 NA MESMA JANELA:
    qualidade  custo  latencia  escalonamento
       ok ------> sobe rollout: 10% -> 25% -> 50% -> 100%
       pior ----> para e volta: rollback para v3

05

Rollback sem redeploy: voltar é trocar um ponteiro

O rollback é a parte que justifica todo o resto. De nada adianta detectar que a candidata regrediu se voltar atrás exige um novo deploy, com build, fila de CI e minutos preciosos enquanto os clientes recebem respostas piores. Como a versão anterior é imutável e continua no registro, e como a escolha de qual versão está ativa vive no manifesto, o rollback é uma única operação: apontar a estável de volta para o id antigo e zerar a candidata. Nada é reconstruído, nenhuma versão é recuperada de backup, porque a versão boa nunca deixou de existir. O tempo entre "a candidata está pior" e "todo mundo voltou para a boa" é o tempo de escrever um valor no manifesto e propagá-lo, segundos, não um ciclo de deploy.

Para o rollback ser instantâneo de verdade, o manifesto não pode estar embutido no bundle da aplicação, senão trocá-lo exigiria justamente o redeploy que se quer evitar. Ele vive num lugar que a aplicação lê em runtime com um cache curto, um registro de configuração, um arquivo em armazenamento observado, uma chave em cache distribuído, e a troca se propaga para todas as instâncias em segundos. O cache precisa de um tempo de vida curto para que o rollback chegue rápido, e o custo dessa leitura frequente é pequeno porque o manifesto é minúsculo. O rollback também merece ser uma ação de primeira classe, um botão ou comando único que faz a troca e registra quem, quando e por quê, para que sob pressão ninguém precise editar JSON à mão no pior momento possível.

// registry/rollout.js
// Promover e reverter sao edicoes do manifesto, nao deploys.
// A versao antiga nunca saiu do registro, entao voltar e instantaneo.

export function promote(manifest, promptName, pct) {
  const e = manifest[promptName];
  if (!e.candidate) throw new Error('sem candidata para promover');
  // Subir a fatia da candidata: 10 -> 25 -> 50. Em 100, vira estavel.
  if (pct >= 100) {
    return { ...manifest, [promptName]: { stable: e.candidate, candidate: null, rolloutPct: 0 } };
  }
  return { ...manifest, [promptName]: { ...e, rolloutPct: pct } };
}

export function rollback(manifest, promptName, reason) {
  const e = manifest[promptName];
  // Voltar e apontar a estavel para o id antigo e zerar a candidata.
  // Nenhum redeploy: a estavel imutavel sempre esteve la.
  logAudit({ action: 'rollback', promptName, from: e.candidate, to: e.stable, reason });
  return { ...manifest, [promptName]: { stable: e.stable, candidate: null, rolloutPct: 0 } };
}

06

O gate de eval barra a versão pior antes do cliente

O rollout gradual limita o dano de uma versão ruim, mas o ideal é a versão ruim nem chegar ao rollout. Isso é papel do gate de eval: uma suíte de casos versionada, com entradas representativas e o que se espera de cada uma, que roda contra a candidata no CI antes da promoção. Alguns casos têm resposta certa verificável, um formato, um campo, uma classificação, e viram asserção direta; outros são subjetivos e usam um modelo como juiz calibrado, comparando a resposta da candidata com um critério. O gate falha o build quando a candidata regride abaixo de um limiar no dataset, e essa reprovação é o que impede que uma edição bem-intencionada de prompt vire uma regressão em produção. O prompt entra no mesmo fluxo de qualquer código: pull request, revisão, teste automático, merge.

  1. Dataset versionado: casos representativos com entrada e resultado esperado, guardados junto do código e crescendo a cada bug real que virou caso de regressão.
  2. Eval objetiva onde dá: formato, campo obrigatório, classificação correta viram asserção determinística que passa ou falha sem ambiguidade.
  3. Juiz calibrado onde não dá: para qualidade subjetiva, um modelo julga contra um critério explícito, aferido contra rótulos humanos para não virar opinião solta.
  4. Gate no CI: a candidata só pode ser promovida se o score no dataset não regride abaixo do limiar; abaixo dele, o build falha e a promoção não acontece.
  5. Comparação contra a estável: o eval roda nas duas versões e compara, para separar uma queda causada pela candidata de uma dificuldade do próprio caso.

O gate de eval e o rollout gradual são duas redes em série, e é essa dupla que dá coragem para mexer no prompt com frequência. O gate pega a regressão previsível, a que o dataset já conhece; o rollout gradual pega a imprevisível, a que só aparece no tráfego real, e limita seu alcance a uma fatia enquanto as métricas decidem. Uma regressão que passe pelas duas ainda encontra o rollback instantâneo como última linha. Com as três em conjunto, versão imutável no registro, gate no CI e rollout com rollback no manifesto, mudar um prompt deixa de ser uma aposta feita direto em produção e vira uma operação de engenharia como qualquer outra, testável, observável e reversível.

FAQ

Perguntas frequentes

Por que versionar o prompt em vez de editá-lo direto em produção?

Porque editar o prompt direto é um deploy disfarçado, o mais arriscado de todos: feito sem revisão, sem histórico e sem rollback. O prompt muda o comportamento observável do sistema, o efeito de uma alteração é difícil de prever, e voltar atrás precisa ser rápido, exatamente as propriedades que exigem versionamento. Quando alguém ajusta uma frase para melhorar um caso, essa frase muda o modelo em todos os casos, e a queda de qualidade só aparece dias depois sem nenhum commit que explique. Tratar o prompt como artefato versionado, com id imutável derivado do conteúdo, dá o histórico do que cada versão era, amarra cada resposta à versão que a gerou e torna a troca de comportamento uma operação explícita e reversível em vez de uma sobrescrita silenciosa.

Como fazer o rollout gradual de um prompt novo sem quebrar a conversa?

Colocando a versão candidata numa fatia pequena do tráfego, cinco ou dez por cento, e escolhendo essa fatia de forma determinística por usuário, não aleatória por requisição. Um hash de um identificador estável do usuário ou da conversa devolve um número entre zero e cem; se ele cai abaixo da porcentagem da candidata, aquele usuário vê a versão nova de forma consistente em toda a sessão, e a conversa não troca de tom no meio. As métricas da candidata são comparadas com as da estável na mesma janela de tempo, cada uma na sua fatia, para isolar o efeito do prompt da variação natural do tráfego. Se qualidade, custo, latência e escalonamento se mantêm ou melhoram, a fatia sobe em degraus; se degradam, para e volta.

O que torna o rollback de um prompt instantâneo?

Duas coisas: a versão anterior ser imutável e continuar no registro, e a escolha de qual versão está ativa viver num manifesto separado do texto. Como a versão boa nunca foi sobrescrita nem apagada, voltar não reconstrói nada, apenas aponta o ponteiro da estável de volta para o id antigo e zera a candidata, uma edição de um valor pequeno. Para isso ser realmente rápido, o manifesto não pode estar embutido no bundle, senão trocá-lo exigiria o redeploy que se quer evitar; ele vive num registro de configuração lido em runtime com cache curto, e a troca se propaga para todas as instâncias em segundos. O rollback merece ser uma ação de primeira classe, um botão que faz a troca e registra quem, quando e por quê, para que ninguém edite JSON à mão sob pressão.

O prompt é código: merece versão, rollout e rollback

Um prompt editado direto em produção é um deploy sem versão, sem revisão e sem volta: quando a qualidade cai, não há como saber o que mudou nem como reverter, porque a versão anterior foi sobrescrita. Tratar o prompt como código, uma versão imutável com id derivado do conteúdo no registro, um manifesto que separa qual versão de quem a recebe, um rollout gradual comparado na mesma janela, um rollback que troca um ponteiro sem redeploy e um gate de eval que barra a regressão no CI, transforma cada mudança de prompt de uma aposta numa operação de engenharia testável e reversível. Posso montar esse fluxo de prompt versionado no seu sistema com LLM, do registro imutável ao gate de eval no CI, para que você mude o prompt com a mesma frequência e a mesma segurança com que muda qualquer código.