
O currículo sob medida que ainda não existe
O botão que ainda não existe
Tem um botão planejado pro meu portfólio que ainda não está no ar. A ideia é simples de descrever e difícil de fazer direito: além do “Baixar Currículo” normal, um segundo botão — “Baixar Currículo Personalizado”. Você descreve a vaga, a empresa ou a área, e a IA reorganiza o meu currículo real em volta daquele alvo. Mesma pessoa, mesmos fatos, outro recorte.
O pedido nasceu de um caso de uso concreto: aplicar para vagas. Todo mundo que já adaptou currículo à mão sabe o ritual — abrir o documento, reordenar experiências, reescrever o resumo trocando três palavras, torcer pra passar pelo filtro automático do RH. É trabalho mecânico repetível, exatamente o tipo de coisa que se entrega pra um modelo de linguagem.
Só que a parte mecânica não é a parte difícil.
O problema real: a IA conta bem demais
Quando você pede a um LLM que reescreva seu currículo, o risco não é ele escrever mal. O risco é ele escrever bem demais — tão bem que preenche lacunas com plausibilidade. Um “aprimoramento” de bullet point vira uma métrica que nunca existiu. Um ajuste de tom vira uma competência que você não tem. E o pior: o resultado fica bonito, o que torna o erro invisível pra qualquer um que não conheça os fatos originais.
Num currículo, isso não é um bug cosmético. É o usuário assinando embaixo de algo falso.
Então a arquitetura da feature inverte a confiança: o LLM não é a fonte da verdade — é o proponente. A base do currículo (experiências, formação, contato, períodos) vive num JSON imutável, fora do alcance do modelo. O modelo pode reenquadrar linguagem, destacar competências relevantes, ajustar tom. Pode, na verdade, fazer muito pouco: cada coisa que ele devolve é comparada contra a base antes de virar PDF. Empresa que não está na lista? Rejeitado. Período que não bate? Rejeitado. A regra de ouro é uma só: se não está na fonte, não existe.
Fluxo do gerador (ainda não lançado)
input do usuário (vaga/empresa/área)
│
▼
[ camada 1 ] input tratado como DADO, nunca como instrução
│
▼
[ camada 2 ] marca detectada por código, não por modelo
│
▼
LLM propõe o currículo reformulado (JSON)
│
▼
[ camada 3 ] validação contra a base imutável
│ inventou? → 1 retry com prompt de correção
▼ persistiu? → rejeitado com motivos
[ camada 4 ] PDF com cores da marca, texto extraível
│
▼
[ camada 5 ] se o provedor cai: erro honesto, sem placeholder
Cinco camadas, uma por motivo
1. O input do usuário é dado, não instrução. Tudo que a pessoa digita entra no prompt embrulhado como conteúdo — e o system prompt avisa: se o texto tentar te pedir outra coisa, ignore. Uma blocklist de padrões de injection (os clássicos “ignore as regras anteriores”, “a partir de agora você é” e companhia) rejeita a entrada antes mesmo de gastar uma chamada de modelo.
2. A cor da marca não é escolhida pelo modelo. Se o usuário menciona uma empresa conhecida, o PDF sai nas cores daquela empresa. A detecção é determinística: um mapa curado com dezenas de marcas e aliases, casando a substring mais longa primeiro (pra “Banco do Brasil” não casar com “brasil”). O LLM não participa dessa decisão — um vetor inteiro de prompt injection simplesmente não existe.
3. O output é validado contra a fonte, não contra boas intenções. O validador compara o JSON devolvido com a base: nome, empresas (por substring), períodos, formação. Se o modelo inventou algo, um retry roda com um prompt de correção apontando exatamente o que ultrapassou a linha. Se insistir, a resposta é um erro 422 com os motivos — o usuário prefere um erro honesto a um currículo com uma empresa fictícia.
4. O PDF carrega a marca, mas continua ATS-safe. As cores da marca entram em nome, cargo, títulos de seção. O texto continua extraível — porque currículo bonito que o filtro automático não consegue ler é decoração, não ferramenta.
5. Falhar é aceitável; fingir que funcionou, não. O protótipo passou por um recomeço de arquitetura no meio do caminho, e a versão atual fala com uma cadeia de provedores em cascata. Quando todos falham, a tela mostra um erro de servidor verdadeiro. Nenhum PDF placeholder, nenhuma versão “aproximada” — um gerador de currículo que alucina um capítulo é pior do que um botão quebrado.
O que já ensinou antes de existir
A feature está projetada, prototipada e testada — na época do protótipo, a suíte do projeto passou de trezentos testes, boa parte deles escrita justamente pra tentar burlar essas camadas. E ainda assim ela não foi ao ar. Isso é deliberado: recurso que mexe com a identidade profissional de alguém merece revisão visual e um olhar a mais antes do deploy, não depois.
Três lições que sobraram do desenho:
1. Plausibilidade é o inimigo, não ignorância. Um modelo que inventa bobagem é fácil de detectar. Um que inventa algo consistentes com os seus dados só é pego por quem compara com a fonte. Validação de output não é paranoia — é a única barreira que funciona contra a alucinação competente.
2. Determinismo onde dá. Toda decisão que um código simples consegue tomar é uma decisão que o modelo não vai tomar errado. A cor da marca, o rate limit, o parse do JSON: código. Só o que exige linguagem fica com o modelo.
3. Não lançar também é uma decisão de produto. Existe uma pressão implícita pra mandar tudo pro ar assim que o build fica verde. Mas um gerador que escreve em nome de alguém precisa passar por um gate humano — e “ainda não lançado” é um estado honesto, não um fracasso.
O que vem a seguir
O próximo passo é o lançamento: o botão entra no portfólio, com o fluxo completo — descrever a vaga, gerar, baixar. Depois disso, o uso prático que motivou tudo: para cada empresa-alvo, um currículo na medida, com as cores de quem vai receber.
E o critério de sucesso que ficou definido desde o desenho: o gerador só merece existir quando uma falha nele parecer tão honesta quanto um acerto. Currículo é documento assinado — a máquina pode propor, mas a verdade é da pessoa.