Tem um curso no meu SaaS chamado The Elixir of Life. Aula de literatura inglesa pro décimo ano no estado de Karnataka, na Índia, explicada em hindi e urdu. Apareceu no catálogo com o meu nome, como se eu tivesse escolhido.

Eu não cadastrei esse curso. Quem cadastrou foi um robô que estava procurando cursos da linguagem Elixir.

Era o outro Elixir

A IA errou feio de Elixir. Mas a culpa foi minha: o juiz respondeu a pergunta que recebeu, e eu esqueci de perguntar uma coisa importante.

Gravei um vídeo mostrando esse sistema por dentro. Os trechos abaixo vêm do código real do CourseShelf.

https://www.youtube.com/watch?v=7MnkJTROtek

Primeiro eu aprovava tudo

O CourseShelf é meu catálogo de cursos gratuitos, a maioria do YouTube, com avaliações da comunidade. É feito em Elixir e Phoenix. Em junho de 2026 eram 111 cursos, entrando uns dois por mês. Catálogo manual cresce na velocidade de quem cadastra.

A primeira automação só procurava candidatos e abria uma issue no GitHub. Era uma tarefa agendada no Claude Code, rodando no meu computador. Quem importava era eu.

Na rodada de 10 de julho vieram dez candidatos. O agente também apontou buracos: não tinha curso de Python nem de TypeScript. No dia seguinte, os dez já estavam no catálogo. Eu aceitei todos.

Resolvi tirar a aprovação do meio e receber um e-mail semanal pra revisar depois. O robô saiu do meu computador e virou parte do app.

A busca não fica com a LLM

Todo sábado às três da manhã em São Paulo, o job pega as oito tags com mais cursos e busca cada nome seguido de full course no YouTube. A consulta considera vídeos longos do último ano e ordena pelos mais vistos.

Quem busca é a API do YouTube. Ela devolve IDs reais, em vez de eu pedir a uma LLM que se lembre de um vídeo e torcer pra ele existir.

Depois entram filtros sem IA: tirar o que já está no catálogo e o que tem menos de sessenta minutos. Cada tópico manda no máximo três candidatos ao juiz, intercalados com os outros tópicos. No fim, entram no máximo cinco cursos por semana.

LimiteO que ele controla
Oito tópicosO tamanho da busca
Três candidatos por tópicoUm assunto não ocupa toda a rodada
Só confiança altaVeredito médio fica de fora
Cinco importaçõesO máximo de entradas erradas numa rodada
E-mail com o motivoO que eu preciso revisar depois

Esses números seguram o estrago. Um modelo melhor não substitui um teto de importações.

O juiz responde num formato fixo

Cada candidato vai pro modelo com título, canal, duração, data e descrição. A instrução é aceitar material estruturado que ensine um assunto e rejeitar podcast, palestra, entretenimento ou trailer de curso pago. Na dúvida, rejeitar.

Eu uso structured output pra receber três campos, em vez de interpretar uma resposta em prosa. Este é o schema real:

# courseshelf-v2/lib/course_shelf/courses/catalog_scout.ex
defp schema do
  %{
    type: "object",
    additionalProperties: false,
    required: ["is_course", "confidence", "reason"],
    properties: %{
      is_course: %{type: "boolean"},
      confidence: %{type: "string", enum: ["high", "medium", "low"]},
      reason: %{type: "string"}
    }
  }
end

Só entra quando is_course é verdadeiro e a confiança é high. Confiança aqui é a classificação do próprio modelo, não uma probabilidade calibrada de acerto.

O código usa DeepSeek pela OpenRouter e temperatura zero pra reduzir a variação. Isso não transforma o resultado numa função perfeitamente determinística, muito menos numa garantia de correção.

O teste passou. A pergunta estava incompleta

Antes de ligar o automático, rodei uma versão só de leitura: faz a busca e o julgamento, mas não importa nem manda e-mail. Foram treze candidatos, seis aceitos e sete rejeitados. Eu concordei com os treze. Entre os rejeitados, tinha até Peppa Pig aparecendo na busca de saúde.

Liguei. E depois entrou The Elixir of Life, uma aula de uma hora e dezoito minutos.

O juiz recebeu uma aula estruturada, explicando um texto inteiro linha por linha, e respondeu que era um curso. Estava certo nessa classificação. O problema aparece no prompt:

# courseshelf-v2/lib/course_shelf/courses/catalog_scout.ex
defp judge_prompt(candidate) do
  """
  Title: #{candidate.title}
  Channel: #{candidate.channel_title}
  Duration: #{candidate.duration_minutes} minutes
  Published: #{candidate.published_at}

  Description:
  #{truncate(candidate.description)}
  """
end

Cadê o tópico da busca? Não está ali. Eu nunca perguntei se aquele curso tinha relação com a linguagem Elixir.

A pergunta errada passou

O mais irônico é que a outra IA, responsável pelas tags, acertou: literatura inglesa e preparação pra prova. O sistema tinha informação suficiente pra perceber a incoerência, mas ninguém conectou as duas decisões.

A correção que eu proponho no vídeo é mandar o tópico junto e exigir que o juiz também avalie essa relação. O vídeo foi gravado com o problema ainda presente; não estou apresentando esse trecho como uma correção já entregue.

Aprovação humana ou carimbo?

O melhor argumento contra automatizar é a confiança no catálogo. Cada curso com meu nome parece uma escolha minha, e uma entrada errada desgasta isso.

Só que eu também vi como minha aprovação funcionava: dez candidatos, dez aceitos. Uma etapa humana que sempre diz sim pode virar só carimbo.

Eu prefiro explicitar o que limita a automação: fonte verificável, formato de resposta, filtros, teto de escrita e revisão com o motivo de cada decisão. E melhorar os testes com o erro que apareceu, porque treze exemplos corretos não cobrem todo o comportamento futuro.

Depois de aprovado, o candidato usa o mesmo importador de um cadastro humano. Entram os jobs de tags, dificuldade, embedding, resumo de comentários e resumo do canal. Não tem um atalho que pula as regras normais do produto.

O sistema errou, eu consigo explicar como o erro entrou e sei qual condição faltou. Essa foi a lição mais útil: se uma condição importa pra publicar no seu produto, ela precisa aparecer na decisão. A IA não vai adivinhar o requisito que você esqueceu de escrever.

Vídeo no YouTube: Automatizei meu SaaS com IA (e deu errado)

Conheça meu trabalho: https://bergholz.com.br/

Obrigado por ler até aqui.

The end