O que é Registro de Templates?
Registro de Templates (Prompt Template Registry) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Guardar prompts como artefatos versionados, com variáveis e histórico.
Prompt Template Registry
Registro de Templates (Prompt Template Registry) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Guardar prompts como artefatos versionados, com variáveis e histórico.
Guardar os prompts como artefatos versionados, com nome, variáveis declaradas, histórico e um processo de publicação. Serve para sistemas com vários prompts em produção, editados por mais de uma pessoa ou por mais de um time.
Uma biblioteca com controle de edições: cada livro tem número de edição, e ninguém reescreve a página de um volume já impresso.
Henrique lidera a plataforma de IA de uma empresa de varejo. São 23 prompts em produção, espalhados por quatro serviços: SDR, atendimento, descrição de produtos e resumo de avaliações. Alguns ficam em constantes no código, outros numa tabela de configuração editada por um painel.
Três problemas aparecem toda semana. Ninguém sabe qual versão gerou a resposta da reclamação. O time de marketing quer ajustar o tom sem esperar deploy, mas não há como testar antes de publicar. E dois serviços usam cópias diferentes da mesma persona, que divergiram com o tempo.
Na terça, um cliente reclama no Procon de uma mensagem do SDR que prometeu desconto de Pix na primeira conversa. Henrique abre o trace: tem o texto gerado, o modelo, a hora. Não tem o prompt. O painel de config foi editado na quarta de manhã. O texto de terça sumiu.
O que falta não é um prompt melhor. É um lugar para os prompts, com as mesmas garantias que o código tem: versão, revisão, teste e rollback.
A pergunta que este padrão responde: como gerenciar dezenas de prompts em produção com rastreabilidade e segurança para mudar?
Crie um registro de templates. Cada template tem:
production, staging, canary, que apontam para versões específicas.O código pede o template pela etiqueta (ou por versão fixa), renderiza com as variáveis e registra nome e versão no trace. Publicar em produção é mover a etiqueta, depois de o eval passar. Voltar atrás é mover a etiqueta de volta.
Isso trata Prompt Sem Versão e Prompt Espalhado. A refatoração que chega aqui é Extrair Template Versionado.
flowchart LR
E((("1. Autor edita"))):::human --> V["2. Nova versão imutável"]:::tool
V --> EV["3. Eval roda na versão nova"]:::tool
EV --> R[("4. Registro: versões e etiquetas")]:::store
R -->|"etiqueta production"| S["5. Serviço renderiza com variáveis"]:::tool
S --> M(["6. Modelo"]):::llm
M --> T[("7. Trace com nome e versão")]:::store
A reclamação do Procon aponta a conversa conv-4412, terça às 16h. O trace agora tem prompt.name=sdr_first_message e prompt.version=14. Henrique abre o registro:
# prompts/sdr_first_message/labels.yaml
production: 14
staging: 15
A versão 14, imutável, ainda está no disco. O corpo diz: “Não ofereça desconto na primeira mensagem. Se o lead perguntar preço, encaminhe para a tabela vigente.” A versão 15, em staging, relaxou essa regra para um teste de conversão. O eval de 15 caiu 0,3 na rúbrica de tom e ainda não ganhou a etiqueta de produção.
Henrique responde ao jurídico com o texto exato que o modelo recebeu. Marketing continua o teste em staging. Ninguém reescreveu o passado.
Compare os dois jeitos de mudar:
| Jeito | O que sobra da versão antiga | Como volta atrás |
|---|---|---|
| Editar a constante ou a linha do banco | Nada. O texto anterior sumiu | Torcer para alguém lembrar |
| Publicar uma versão nova e mover a etiqueta | Todas as versões, com eval | Apontar production de volta para 14 |
schema Template { name: string, version: int, model: string, variables: map[string, type],
body: string, author: string, reason: string, eval_result: EvalSummary? }
function publish(name, body, variables, author, reason):
v = registry.create_version(name, body, variables, author, reason) # 2. imutável
v.eval_result = eval_harness.run(name, v) # 3
if v.eval_result.regressed(baseline=registry.get(name, label="production")):
return reject(v)
registry.set_label(name, "staging", v.version)
return v
function promote(name, version):
registry.set_label(name, "production", version) # rollback = apontar de volta
function render(name, label, values):
t = registry.get(name, label=label) # 5
assert set(values) == set(t.variables) # nada faltando ou sobrando
trace.span("prompt", name=t.name, version=t.version) # 7
return t.render(values)
Um registro não substitui o eval. Sem um Eval Harness na publicação, a etiqueta de produção vira um botão de “publicar e torcer”.
Comece pelo repositório. Arquivos YAML com labels.yaml e um vN.yaml por versão resolvem a maior parte dos casos. Só suba para um serviço com painel quando marketing ou jurídico precisarem editar sem esperar merge, e mesmo aí o eval continua sendo o portão.
{{ "formal" if segment == "industria" else "proximo" }} parece inofensivo até ter oito ramos. Tire a decisão para o código, deixe o template com variáveis já resolvidas.cupom às vezes entra e às vezes não, declare duas versões ou um campo sempre presente (string vazia). Renderizar pela metade é o bug que o schema existe para pegar.canary recebe uma fração do tráfego antes de virar production. Combina com a ideia de deploy sombra: a versão nova responde em paralelo, sem chegar ao cliente, até o eval de produção acompanhar o de staging.| Aspecto | O que acontece | Como aparece no trace |
|---|---|---|
| Latência | Carregar o template soma milissegundos; use cache local | span prompt curto |
| Custo | Nenhum custo de modelo extra; o eval na publicação custa | custo do eval por publicação |
| Falha: variável faltando | Prompt renderizado pela metade | erro de renderização, nunca uma chamada |
| Falha: etiqueta errada | Versão não testada em produção | versão no trace sem resultado de eval |
| Falha: registro fora do ar | Serviços não conseguem carregar o prompt | erros de carregamento; mantenha cache local da última versão |
| Falha: lógica no template | Condições complexas dentro do texto | prompts renderizados diferentes do esperado |
O eval na publicação não é barato, e não precisa ser. Rode o conjunto pequeno (casos que pegam tom, recusa de desconto, variáveis obrigatórias) em toda versão. O conjunto grande roda de noite, na etiqueta staging.
{ "template": "sdr_first_message", "candidate": 15, "baseline": 14, "eval": "evals/sdr.jsonl", "block_if": "tom_rubrica < baseline - 0.2" }
Um caso concreto do eval: a mensagem não pode citar preço nem Pix na primeira fala. Se a versão 15 quebrar isso, a etiqueta de produção não se move.
prompt.name e prompt.version em todo span de modelo.Ensaie o rollback. Mover production: 15 para production: 14 tem que ser um passo documentado, testado uma vez em staging, não uma invenção no meio do incidente.
Templates são texto, e texto costuma receber atalho. Uma chave de API colada no corpo, “só para o modelo chamar o CRM”, vira o smell Segredo no Prompt. O registro não deve aceitar isso: variáveis de credencial não existem, ferramentas recebem segredo no backend. Veja Tirar Segredo do Prompt.
Quem pode publicar em produção é um papel separado de quem pode propor versão. O painel de marketing escreve em staging. Engenharia (ou um bot do eval) move a etiqueta.
Saída do template ainda vai para o modelo junto com dados do lead. Trate o corpo renderizado como o que é: instrução sua mais dados de fora. A Saída Estruturada no lado da resposta reduz o dano de um template que peça demais.
| Prós | Contras |
|---|---|
| Toda resposta aponta para o texto exato | Mais uma peça de infraestrutura |
| Rollback em segundos | Exige disciplina de publicar pelo registro |
| Pessoas fora da engenharia propõem mudanças com segurança | Eval por publicação custa |
| Migração de modelo rota a rota | Lógica escondida no template ainda é possível |
As duas abas implementam um registro simples em arquivos versionados, com etiqueta de produção e registro da versão no trace.
Python. Use quando os templates vivem no repositório e você quer um registro sem infraestrutura nova.
from dataclasses import dataclass
from pathlib import Path
import yaml
from jinja2 import Environment, StrictUndefined
ROOT = Path("prompts")
jinja = Environment(undefined=StrictUndefined) # variável faltando vira erro
@dataclass(frozen=True)
class Template:
name: str
version: int
model: str
variables: tuple[str, ...]
body: str
def render(self, **values) -> str:
missing = set(self.variables) - set(values)
extra = set(values) - set(self.variables)
if missing or extra:
raise ValueError(f"{self.name}@{self.version}: faltando {missing}, sobrando {extra}")
return jinja.from_string(self.body).render(**values)
def load(name: str, label: str = "production") -> Template:
labels = yaml.safe_load((ROOT / name / "labels.yaml").read_text()) # {"production": 14, "staging": 15}
version = labels[label]
data = yaml.safe_load((ROOT / name / f"v{version}.yaml").read_text()) # arquivos nunca editados depois de criados
return Template(name, version, data["model"], tuple(data["variables"]), data["template"])
# uso
tpl = load("sdr_first_message")
prompt = tpl.render(lead_name="Ana", company="Padaria Sol", segment="varejo",
pricing_policy="sem preço na primeira mensagem", campaign="lançamento")
span.set_attributes({"prompt.name": tpl.name, "prompt.version": tpl.version}) # 7O que você ganhou: versões imutáveis revisadas por pull request, e rollback editando uma linha de labels.yaml. O que você perdeu: mudanças exigem merge; para edição sem deploy, ponha o mesmo formato atrás de uma API.
TypeScript. Use quando os serviços em Node carregam templates de um registro central com cache local.
type Template = { name: string; version: number; model: string; variables: string[]; body: string };
const cache = new Map<string, { tpl: Template; fetchedAt: number }>();
export async function load(name: string, label = "production"): Promise<Template> {
const key = `${name}:${label}`;
const hit = cache.get(key);
if (hit && Date.now() - hit.fetchedAt < 60_000) return hit.tpl;
try {
const res = await fetch(`${process.env.PROMPT_REGISTRY_URL}/templates/${name}?label=${label}`);
if (!res.ok) throw new Error(`registro respondeu ${res.status}`);
const tpl = (await res.json()) as Template;
cache.set(key, { tpl, fetchedAt: Date.now() });
return tpl;
} catch (err) {
if (hit) return hit.tpl; // registro fora do ar: usa a última versão conhecida
throw err;
}
}
export function render(tpl: Template, values: Record<string, string>): string {
const missing = tpl.variables.filter((v) => !(v in values));
const extra = Object.keys(values).filter((v) => !tpl.variables.includes(v));
if (missing.length || extra.length) {
throw new Error(`${tpl.name}@${tpl.version}: faltando ${missing}, sobrando ${extra}`);
}
return tpl.body.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, k: string) => values[k]);
}O que você ganhou: mudanças sem deploy, com cache que protege contra o registro fora do ar. O que você perdeu: o portão de eval precisa existir no próprio registro, antes de mover a etiqueta.
Registro de Templates (Prompt Template Registry) é um padrão de projeto de IA para apps com LLM e agentes, do grupo Composição. Guardar prompts como artefatos versionados, com variáveis e histórico.
Prompt Preso ao Modelo, Prompt Espalhado e Prompt Sem Versão.
Nos cenários reais SDR de vendas automatizado, Pipeline de criação de conteúdo e Bot de suporte com escalonamento humano.