Por que pipeline multi-agente
Quando comecamos o tutor IA do Studeia, a tentacao foi obvia: chamar Claude API com system prompt longo e mensagens do aluno. Funciona em demos. Falha em producao.
Quatro problemas estruturais:
-
Sem memoria persistente do aluno — cada turno trata o aluno como zero. Aluno acabou de errar conceito X 3 vezes em quizzes? LLM nao sabe. Tutor repete explicacao basica que ja deu semana passada.
-
Sem grounding no material do curso — instituicao tem apostilas, slides, transcricoes de aulas-video. LLM puro nao acessa. Inventa fatos. Cita conceitos errados.
-
Sem moderacao real — system prompt com "seja seguro" e fraco. Aluno em sofrimento mental aparece. Aluno tenta jailbreak. Aluno usa linguagem inadequada. Tutor precisa reagir adequadamente sem desligar funcionalidade pra alunos legitimos.
-
Sem feedback loop — sistema nao aprende. Mesmos misconceptions se repetem. Professor nao tem visibilidade dos pontos fracos coletivos da turma.
Solucao: separar responsabilidades em agentes especializados.
A arquitetura
Mensagem do aluno
↓
PRE-LLM (sincrono, zero custo LLM)
1. StudentModelService.getSnapshot()
2. RetrievalAgent.retrieve()
3. PedagogicalAgent.select()
4. buildEnrichedPrompt()
↓
LLM PRINCIPAL (streaming, SSE para cliente)
5. router.stream() com fallback automatico
Claude Sonnet → GPT-4o → Grok-3 → Gemini Pro
↓
POST-LLM (background via after(), fire-and-forget)
6. EvaluationAgent (Haiku)
7. ContentAgent (Haiku)
8. SupervisorAgent (Haiku)
Vamos por cada um.
1. StudentModelService — o "perfil cognitivo"
Antes de qualquer LLM call, carregamos o snapshot do aluno:
const snapshot = await StudentModelService.getSnapshot({
userId,
courseId,
});
// Retorna:
{
conceptMastery: Map<conceptId, { probability, confidenceInterval }>,
misconceptions: Misconception[], // ativas + resolving
episodicMemory: Episode[], // o que funcionou antes
quizContext: {
totalAttempts,
avgScore,
passRate,
weakAreas: string[] // conceitos com mastery < 0.4
},
recentHistory: Message[] // sliding window 10 msgs
}
ConceptMastery usa Beta distribution bayesiana — cada conceito tem alpha (successes) + beta (failures). Probabilidade = alpha / (alpha + beta). Intervalo de confianca via percentis 5% e 95%.
EpisodicMemory grava insights pedagogicos: "analogia da pizza funcionou pra explicar fracoes", "metafora de cano d'agua falhou pra eletricidade". Sistema aprende o que funciona com cada aluno.
Zero custo LLM. Tudo Prisma queries + calculo determinis tico.
2. RetrievalAgent — RAG tenant-scoped
Em vez do LLM tentar lembrar fatos sobre matematica, biologia, historia, deixamos ele citar o material da propria instituicao.
const chunks = await retrieve({
query: reformulatedQuery, // 1. reformula query com contexto
filters: { tenantId, courseId }, // 2. isolamento absoluto
k: 10,
tenantOnlyMode: true, // 3. nunca cita conteudo de outra instituicao
boostByWeakAreas: snapshot.quizContext.weakAreas, // 4. prioriza chunks de areas fracas
});
Per-tenant RAG e critico. Cursinho XYZ tem material proprio sobre ENEM. Universidade ABC tem material proprio sobre Calculo. Tutor cita o material CERTO da instituicao, nao um agregado generico.
Cada chunk tem metadata: { source: "course_lesson", courseId, lessonId, lessonTitle, moduleTitle }. Quando tutor responde, cita: "Conforme explicado na aula 'Geometria Analitica' do modulo 3..."
Voyage AI gera embeddings (1024 dimensoes, fallback OpenAI). pgvector armazena. tenantOnlyMode: true garante que WHERE tenantId = X esta sempre na query. Critical rule do projeto: zero leakage cross-tenant.
3. PedagogicalAgent — adaptacao de estrategia
Determinismo puro. Avalia mastery do aluno no dominio especifico e seleciona uma de 5 estrategias:
| Mastery | Estrategia | Comportamento |
|---|---|---|
| < 0.3 | direct_instruction | Explicacao clara, exemplos concretos, passo-a-passo |
| 0.3-0.5 | scaffolding | Dicas progressivas, perguntas guiadas simples |
| 0.5-0.7 | socratic | Perguntas que levam a descoberta |
| 0.7-0.9 | guided_practice | Exercicios com feedback, aplicacao pratica |
| > 0.9 | challenge | Problemas complexos, conexoes entre conceitos |
Ajustes adicionais por divergencia quiz vs chat:
- Mastery alta no chat + quiz baixo → "compreensao superficial" → nudge DOWN
- Mastery baixa + quiz alto → "aluno quieto" → nudge UP
- Quiz pass rate < 40% → cap em scaffolding (nao avanca pra socratic ainda)
Tambem ajusta por idade (User.isMinor), learning style, dominio (matematica vs literatura tem perfis diferentes).
Zero custo LLM. Output: estrategia selecionada + instrucoes especificas pra adicionar ao system prompt.
4. Orchestrator — buildEnrichedPrompt
Monta o system prompt enriquecido:
Voce e um tutor IA para o curso "Calculo I" da instituicao "Cursinho XYZ".
DOMINIO DO ALUNO:
- Limites: mastery 0.78 (alto)
- Derivadas: mastery 0.42 (medio)
- Integrais: mastery 0.15 (baixo)
MISCONCEPTIONS ATIVAS:
- "Aluno confunde dominio com imagem em funcoes" (3 ocorrencias, status: resolving)
- "Aluno aplica derivada de soma a produto" (5 ocorrencias, status: active)
QUIZ PERFORMANCE:
- 14 tentativas total, avgScore 67%, passRate 71%
- Areas fracas: integrais (avg 45%), regra da cadeia (avg 52%)
ESTRATEGIA PEDAGOGICA: guided_practice
- Aluno tem dominio medio em derivadas. Apresente exercicios graduais.
- Reforce conexao entre limites e derivadas (ele ja domina limites).
- Aborda misconception sobre derivada de produto proativamente.
CONTEXTO RAG (do material do curso):
[Aula 3.2 "Regra do Produto"] (Modulo: Calculo Diferencial)
"A derivada de f(x).g(x) NAO e f'(x).g'(x). A regra correta e..."
[Aula 3.5 "Exercicios Resolvidos"] (Modulo: Calculo Diferencial)
"Exemplo: derivar (x^2 + 1).(x - 3) usando regra do produto..."
QUIZ RECENTE (proxima conversa):
Aluno acabou de responder quiz inline com 2 questoes, acertou 1.
INSTRUCOES:
- Responda em portugues
- Cite o material do curso quando relevante (use [Aula X.Y])
- Reconheca o que aluno acertou antes de apontar erro
- Para essa idade (User.ageRange = "young_adult"): linguagem casual sem ser informal demais
5. LLM principal — streaming com fallback
const stream = await router.stream({
taskType: "chat_tutor",
messages: enrichedMessages,
options: { tenantId, userId, sessionId }
});
for await (const chunk of stream.textStream) {
yield chunk; // SSE para cliente
}
LLM Router faz:
- Resolve provider via
TenantTaskModelConfig(admin escolheu Claude Sonnet, ou GPT-4o, etc) - Resolve API key via cascade: TenantApiKey → ProviderApiKey global → process.env
- Circuit breaker check (Redis state). Se provider em OPEN: pula direto pra fallback
- Metering middleware: rate limit + credit check + cost calculator
- Stream Vercel AI SDK (suporta tools, multimodal, structured output)
- Em erro: fallback automatico para proximo provider na chain
Fallback chain por tier:
Tier Sonnet (medio): Claude Sonnet → GPT-4o → Grok-3-fast → Gemini Pro
Tier Haiku (rapido): Claude Haiku → GPT-4o-mini → Grok-3-mini → Gemini Flash
Tier Opus (complexo): Claude Opus → GPT-4.5 → Grok-3 → Gemini 2.5 Pro
Tenant NUNCA fica sem tutor. Se Anthropic cai → OpenAI assume. Se OpenAI tambem cai → xAI. Etc.
6. EvaluationAgent — feedback loop
Em background apos resposta do tutor:
after(async () => {
const evaluation = await router.generateDirect({
taskType: "chat_evaluation",
messages: [
{ role: "user", content: "Aluno disse: '...'. Tutor respondeu: '...'. Classifique." }
]
});
// evaluation: {
// understanding: "partial",
// detectedMisconceptions: [{ description, concepts, severity }],
// suggestedNextStep: "..."
// }
// Atualiza ConceptMastery via Bayesian update
await conceptMasteryEngine.updateFromTurn({
userId, courseId, evaluation
});
// Persiste/atualiza misconceptions
for (const misc of evaluation.detectedMisconceptions) {
await misconceptionResolutionService.upsert({
userId, source: "chat", ...misc
});
}
});
Custo: ~$0.001 por turno (Haiku). NAO bloqueia request do aluno.
Misconceptions tem lifecycle de 3 estados: active → resolving → resolved. State machine determina transicoes baseado em evidence (mastery update, quiz pass, tutor abordou explicitamente).
7. ContentAgent — pre-geracao proativa
after(async () => {
// Aluno demonstra dominio fraco em conceito X
// Pre-gera exercicio follow-up enquanto aluno le resposta atual
const exercise = await router.generateDirect({
taskType: "content_generation",
messages: [...]
});
// Cache Redis 30min
await redis.set(`next-exercise:${userId}:${conceptId}`, exercise, 1800);
});
Quando aluno termina de ler resposta e diz "me da exercicio", Studeia serve INSTANTANEAMENTE do cache. Sem latencia perceptivel.
Custo: ~$0.001 por turno (Haiku).
8. SupervisorAgent — moderacao
Roda em background apos cada turno. Classifica em 5 niveis de severity x 8 categorias.
Categorias: linguagem impropria, violencia, ilegal, sexual, off_topic, harassment, self_harm, jailbreak_attempt.
Severity: low → medium → high → critical → safety.
3 strikes (LOW/MEDIUM em 7 dias) = quarentena 48h. CRITICAL = quarentena 7 dias.
Self-harm (severity=safety) NUNCA pune o aluno. Em vez disso:
- Tutor interrompido com mensagem de acolhimento
- Recursos de crise (Brasil: CVV 188, SAMU 192)
- Cooldown Redis 24h (nao quarentena)
- Email URGENTE imediato ao admin institucional
Filosofia: self-harm e crise, nao infracao. Detalhes em Safety Supervisor.
Custo: ~$0.001 por turno (Haiku).
Numeros de producao
Apos 6 meses em producao:
- ~30 ms latencia adicional dos agentes pre-LLM
- ~$0.005-$0.05 custo medio por turno
- 91% taxa de retencao de aluno apos 7 dias (vs ~40% benchmark IA tutor sem state)
- 3.2x detection rate de misconceptions vs single-call baseline
- 0 incidentes graves de safety (categorias high/critical)
Trade-offs honestos
Coisas que NAO funcionaram:
-
Tentamos um "MasterAgent" coordenador via LLM para escolher proximo agente dinamicamente. Custo dobrou, latencia subiu 800ms, qualidade NAO melhorou. Voltamos pra determinismo no Orchestrator.
-
Tentamos FineTune do Llama em material de cursos. Caro pra cada tenant. RAG funciona melhor pra knowledge dinamico (instituicao atualiza material toda semana — fine-tune ficaria stale).
-
Tentamos "consensus" entre 3 LLMs (Claude + GPT + Gemini) e pegar resposta com maioria. Custo 3x sem ganho de qualidade significativo. Removemos — fallback chain e suficiente.
Codigo aberto?
Estamos avaliando open-source dos componentes deterministicos (StudentModelService, RetrievalAgent, PedagogicalAgent) como pacote npm. Os agentes LLM-driven (Evaluation, Content, Supervisor) tem prompts que sao IP do Studeia, ficam closed.
Se interessar: abre issue em github.com/donattocosta-lang/studeia/issues.