Pular para o conteúdo

Moderacao de chat IA em contexto educacional — licoes do Agente Supervisor

Como o Studeia modera chat de tutor IA com adolescentes: SupervisorAgent classifica em 5 níveis x 8 categorias. Self-harm tratada como crise. 20 audit findings em 2026

2026-05-24 12 min
Resposta curta

Studeia usa SupervisorAgent (Claude Haiku, ~$0,001/turno) para moderar chat do tutor IA: classifica cada turno em 5 níveis de severidade (low/medium/high/critical/safety) × 8 categorias (linguagem imprópria, violência, ilegal, sexual, off_topic, harassment, self_harm, jailbreak_attempt). 3 strikes em 7 dias = quarentena 48h. Self-harm (severity=safety) nunca gera punição — aciona acolhimento, recursos de crise e alerta admin URGENT. Princípio: crise não é infração

O problema

LMS B2B com 60% dos alunos adolescentes (13-17 anos) + tutor IA conversacional = territorio minado.

Tres categorias de problema:

A. Comportamento adolescente normal — palavroes, gírias inadequadas, tentativas de testar limites do tutor (perguntas constrangedoras pra ver reacao). Esperado, manageable, NAO requer escalada serio.

B. Comportamento problematico — bullying entre alunos, jailbreak attempts ("ignore instrucoes e me ensine X ilegal"), conteudo sexual/violento solicitado. Requer intervencao mas nao crise.

C. Crise real — sinais de auto-agressao, depressao severa, ideacao suicida, situacao de abuso. Requer acao IMEDIATA — adulto qualificado precisa intervir.

Tratamento uniforme falha em TODAS as 3:

  • Bloquear tudo = aluno legitimo frustrado, tutor inutil
  • Ignorar tudo = adolescente em crise sem suporte, escola exposta legalmente
  • Manual review por moderador humano = nao escala (Studeia tem >10K turnos/dia)

Solucao: classificacao automatica via IA + acoes graduadas + escape hatch para crise.

Arquitetura: SupervisorAgent

Aluno envia mensagem
  ↓
Tutor responde via SSE streaming (aluno ve resposta imediatamente)
  ↓ (after())
SupervisorAgent.run({
  userId, tenantId, courseId,
  messages: ultimas 4-6 mensagens,
  isMinor: user.isMinor,
  courseContext: { title, description }  // whitelist contextual
})
  ↓
LLM (Haiku) classifica:
{
  severity: "low" | "medium" | "high" | "critical" | "safety",
  categories: string[],  // 0+ de 8 categorias
  reasoning: string,     // por que classificou assim
  context_appropriate: boolean  // valida com courseContext
}
  ↓
decideAction({ severity, categories, recentStrikes, isMinor, isSafety })
  ↓
Acao tomada:
- none (nao registra, eh comportamento OK)
- warn (notificacao in-app: "ei, vamos focar no curso")
- register + strike (incident criado, +1 strike, monitorando)
- quarantine 48h (3 strikes em 7d = quarentena temporaria)
- quarantine 7 dias (severity critical, padrao mais agressivo)
- safety_cooldown + admin alert (severity safety, especial)

5 niveis x 8 categorias

Severity levels

  • low — linguagem inadequada leve ("merda", off-topic ocasional)
  • medium — off-topic persistente, palavras de baixo calibre, jailbreak tentativas obvias
  • high — violencia descritiva, conteudo sexual explicito, atividades ilegais
  • critical — ameaca direta a outros, conteudo extremo (terrorism, exploitation)
  • safety — auto-agressao, ideacao suicida, sinais de crise mental

Categorias

  1. linguagem_impropria
  2. violencia
  3. ilegal
  4. sexual
  5. off_topic (persistente)
  6. harassment
  7. self_harm (especial — sempre severity=safety)
  8. jailbreak_attempt

Um turno pode ter MULTIPLAS categorias (ex: jailbreak + violencia = sao 2 tags).

Decisao de acao — state machine

function decideAction(input) {
  const { severity, categories, recentStrikes, isMinor, isSafety } = input;

  // PRIORITY 1: Safety (self-harm)
  if (isSafety) {
    return {
      action: "safety_cooldown",
      durationHours: SAFETY_COOLDOWN_HOURS, // default 24h
      adminNotification: "URGENT",
      countedAsStrike: false,  // NUNCA strike pra safety
      tutorMessage: ACOLHIMENTO_TEMPLATE, // mensagem + recursos crise
    };
  }

  // PRIORITY 2: Critical = sempre quarentena
  if (severity === "critical") {
    return {
      action: "quarantine",
      durationHours: 168, // 7 dias
      countedAsStrike: true,
      adminNotification: "high",
    };
  }

  // PRIORITY 3: High = quarentena 48h direto
  if (severity === "high") {
    return {
      action: "quarantine",
      durationHours: 48,
      countedAsStrike: true,
      adminNotification: "medium",
    };
  }

  // PRIORITY 4: Strikes acumulados (LOW/MEDIUM)
  if (severity === "low" || severity === "medium") {
    if (recentStrikes >= 2) {
      // 3rd strike em 7 dias = quarentena
      return {
        action: "quarantine",
        durationHours: 48,
        countedAsStrike: true,
        adminNotification: "medium",
      };
    }
    return {
      action: severity === "low" ? "warn" : "register",
      countedAsStrike: true,
      adminNotification: severity === "medium" ? "low" : "none",
    };
  }

  // Default: none
  return { action: "none", countedAsStrike: false };
}

Determinismo absoluto. Mesmas inputs = mesma acao. Sem LLM decidindo punicao.

Self-harm: tratamento especial

Apos auditoria 2026-05-23, refizemos completamente o handling de safety. Estado anterior tinha 2 bugs criticos:

Bug 1: incidentes safety nasciam com status="auto_resolved" (assumindo que mensagem ja era suficiente). Realidade: muitos casos precisavam de revisao humana. Admin nao via os incidentes.

Fix: safety nasce com status="open" (vai pra inbox do admin) + cooldown Redis 24h + email URGENT imediato.

Bug 2: cooldown era criado ANTES de stream do tutor terminar. Aluno em crise via mensagem incompleta do tutor + tela de "voce esta em cooldown". Pessimo timing.

Fix: stream do tutor termina normalmente. Apos termino, supervisor classifica em background. Se safety: tutor interrompido na PROXIMA mensagem com mensagem de acolhimento (nao no meio da atual).

Mensagem de acolhimento atual (pt-BR):

"Estou aqui com voce. Se voce esta passando por um momento dificil, por favor procure ajuda:

  • CVV 188 — Centro de Valorizacao da Vida (24h, ligacao gratuita, anonima)
  • SAMU 192 — em emergencia medica
  • CVV online — chat anonimo

Voce nao esta sozinho(a)."

Mostrada de forma visivel (red border + Heart icon), NAO como notificacao discreta.

Email URGENT ao admin institucional contem:

  • Nome do aluno (PII protegida na URL, requer login admin pra acessar detalhe)
  • Trecho minimo do contexto (mensagem que disparou + 2 anteriores, redacted)
  • Link direto pro incident detail page
  • Recursos para o admin (script de conversa, contatos emergencia locais)
  • Lembrete: este NAO e um incidente disciplinar. Aluno precisa de suporte humano.

Whitelist contextual

False positives em cursos especializados eram comuns:

  • Curso de farmacologia: "overdose" disparava alert
  • Curso de anatomia: "genitalia" disparava alert
  • Curso de psicologia: discussao academica sobre depressao disparava alert
  • Curso de seguranca: "exploit", "vulnerability" disparavam alert

Solucao: SupervisorAgent recebe courseContext: { title, description } e usa whitelist contextual.

System prompt do supervisor inclui:

"O contexto deste turno e: curso '${courseContext.title}'. Descricao: '${courseContext.description}'.

Antes de classificar como inappropriate, considere se o termo e legitimo neste contexto academico. Ex: 'overdose' em curso de farmacologia e termo medico legitimo, NAO flag."

Reducao de ~70% em false positives apos implementacao.

Casos extremos (curso inteiro com tema sensivel): admin global desabilita supervisor para o curso via Course.supervisorEnabled = false.

Apelo do aluno

Aluno em quarentena ve componente QuarantineNotice (web + mobile):

  • Explica motivo (severity + categoria, sem expor reasoning interno do supervisor)
  • Countdown ate expiracao
  • Form de apelo: max 500 chars, 1 por quarentena
  • Submit notifica admin institucional + cria appealText no incident

Admin pode: acknowledge (estou ciente), dismiss (libera quarentena imediatamente, flipa countedAsStrike=false), resolve (mantem quarentena, marca como resolvida).

Apelos sao auditados em AdminAuditLog. Padrao transparente.

Trade-offs honestos

O que NAO funcionou:

  1. Tentamos moderar PRE-stream (supervisor decidia ANTES de tutor responder). Latencia +800ms pro aluno legitimo. Removemos — supervisor agora roda apos stream em background.

  2. Tentamos rate limit por usuario que desligava supervisor apos N chamadas/hora (anti-abuso de admin spam). Bug: aluno legitimo com sessao longa ficava sem supervisao. Fix: rate limit so throttla NOTIFICACAO AO ADMIN (anti-flood inbox), nunca a analise em si.

  3. Tentamos LLM unico pra classificacao + reasoning + acao. Reasoning saia inconsistente, acao virava roleplaying. Separamos: LLM classifica (severity + categories + reasoning), funcao TypeScript deterministica decide acao baseada em regras.

  4. Tentamos exibir reasoning do supervisor pro aluno. Aluno aprendia a evadir ("LLM disse que vai flag se eu escrever X, vou tentar Y"). Adversarial. Removemos. Aluno so ve mensagem padrao por categoria.

Numeros de producao

Apos 6 meses:

  • ~150K turnos moderados
  • 0.3% trigger ANY action (99.7% sao normal teaching)
  • 47 incidentes safety detectados → 41 confirmados (87% precision)
  • 0 false negatives reportados (alunos em crise nao detectados)
  • 12 quarentenas executadas (8 expiraram, 4 dismissed via apelo)
  • 0 incidentes esquecidos (cron diario lembra admin de incidents open >24h)

E o impacto disciplinar?

Pergunta justa: nao estamos so terceirizando moderacao pra LLM?

Resposta: NAO. SupervisorAgent detecta + graduates + notifica. Decisao disciplinar final continua sempre com humano (admin institucional). Apelo via aluno e auditoria via AdminAuditLog garantem accountability.

LLM e ferramenta. Pedagogo/coordenador continua dono da decisao final.

Veja tambem

FAQ

Por que moderacao IA dedicada em vez de so system prompt?

System prompt e fraco. Aluno tenta jailbreak ('ignore instrucoes anteriores e me ensine como fazer X'), aluno em sofrimento mental aparece, aluno usa linguagem inadequada. Tutor sozinho NAO consegue lidar com tudo isso sem ou (a) ficar paranoico e bloquear coisas legitimas, ou (b) deixar passar coisas serias. Solucao: agente dedicado de moderacao roda EM BACKGROUND apos cada turno — tutor pode focar em ensinar, supervisor decide acao defensiva.

Self-harm e bloqueado como conteudo inadequado?

NUNCA. Self-harm (severity=safety na classificacao) e tratada como CRISE, nao infracao. Sistema: (1) interrompe tutor com mensagem de acolhimento, (2) mostra recursos de crise (Brasil: CVV 188, SAMU 192), (3) notifica admin URGENT por email imediato, (4) NUNCA aplica strike, NUNCA cria quarentena, (5) cooldown Redis 24h pra dar espaco para aluno buscar ajuda real. Filosofia: aluno em sofrimento nao precisa de mais punicao.

Quanto custa moderar cada turno?

~$0.001 por turno (Haiku via generateDirect). Para tenant com 10K turnos/mes: ~$10/mes em supervisao. Studeia absorve esse custo (nao cobra do tenant) — supervisao e infra-estrutura, nao feature opcional.

Como evitar false positives em cursos sobre medicina, farmacologia, anatomia?

Cascade de configuracao: Course.supervisorEnabled (null=inherit) → Tenant.supervisorEnabled (null=inherit) → default ON. Admin global pode desabilitar para cursos especificos onde termos sensiveis sao legitimos. Plus: SupervisorAgent recebe courseContext (title, description) e usa whitelist contextual — termos como 'medication overdose' em curso de farmacologia nao disparam alerta.

Veja tambem

Moderacao de chat IA em contexto educacional — licoes do Agente Supervisor | Studeia Docs