Saltar al contenido
Studeia Docs
AI-assisted translation — last updated 2026-05-24. For original (pt-BR or en-US), use the language switcher.

Cómo construimos un pipeline multi-agente de tutor IA en EAD

Tutor IA Studeia: pipeline multi-componente con orquestación, modelo del alumno, retrieval, pedagogía, respuesta LLM y tareas de evaluación/contenido/seguridad en background, fallback Claude/GPT/Grok/Gemini, RAG por tenant y ConceptMastery bayesiano

2026-05-24 15 min
Resposta curta

El tutor IA de Studeia es un pipeline pedagógico multi-componente: Orchestrator coordina, StudentModel carga contexto bayesiano, RetrievalAgent ejecuta RAG por tenant, PedagogicalAgent elige estrategia y el LLM principal responde (fallback automático Claude/GPT/Grok/Gemini). En background pueden correr tareas LLM-backed como EvaluationAgent, ContentAgent y SupervisorAgent. Los componentes determinísticos tienen cero coste LLM; las tareas de background suelen usar Haiku (~$0.003). Coste total por turno: $0.005–$0.05

Por qué un pipeline multi-agente

Cuando empezamos el tutor IA de Studeia, la tentación fue obvia: llamar a la API de Claude con un system prompt largo y los mensajes del alumno. Funciona en demos. Falla en producción.

Cuatro problemas estructurales:

  1. Sin memoria persistente del alumno — cada turno trata al alumno desde cero. ¿El alumno acaba de fallar el concepto X 3 veces en quizzes? El LLM no lo sabe. El tutor repite la explicación básica que ya dio la semana pasada.

  2. Sin grounding en el material del curso — la institución tiene apuntes, slides, transcripciones de clases en video. El LLM puro no accede a ellos. Inventa hechos. Cita conceptos erróneos.

  3. Sin moderación real — un system prompt con "sé seguro" es débil. Aparece un alumno en sufrimiento mental. Un alumno intenta jailbreak. Un alumno usa lenguaje inadecuado. El tutor necesita reaccionar adecuadamente sin desactivar la funcionalidad para alumnos legítimos.

  4. Sin feedback loop — el sistema no aprende. Los mismos misconceptions se repiten. El profesor no tiene visibilidad de los puntos débiles colectivos de la clase.

Solución: separar responsabilidades en agentes especializados.

La arquitectura

Mensaje del alumno
  ↓
PRE-LLM (síncrono, cero costo LLM)
  1. StudentModelService.getSnapshot()
  2. RetrievalAgent.retrieve()
  3. PedagogicalAgent.select()
  4. buildEnrichedPrompt()
  ↓
LLM PRINCIPAL (streaming, SSE al cliente)
  5. router.stream() con fallback automático
     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 uno.

1. StudentModelService — el "perfil cognitivo"

Antes de cualquier llamada LLM, cargamos el snapshot del alumno:

const snapshot = await StudentModelService.getSnapshot({
  userId,
  courseId,
});

// Retorna:
{
  conceptMastery: Map<conceptId, { probability, confidenceInterval }>,
  misconceptions: Misconception[],  // activas + resolving
  episodicMemory: Episode[],         // qué funcionó antes
  quizContext: {
    totalAttempts,
    avgScore,
    passRate,
    weakAreas: string[]              // conceptos con mastery < 0.4
  },
  recentHistory: Message[]           // sliding window 10 msgs
}

ConceptMastery usa distribución Beta bayesiana — cada concepto tiene alpha (successes) + beta (failures). Probabilidad = alpha / (alpha + beta). Intervalo de confianza via percentiles 5% y 95%.

EpisodicMemory registra insights pedagógicos: "la analogía de la pizza funcionó para explicar fracciones", "la metáfora del caño de agua falló para electricidad". El sistema aprende qué funciona con cada alumno.

Cero costo LLM. Todo son queries Prisma + cálculo determinístico.

2. RetrievalAgent — RAG tenant-scoped

En lugar de que el LLM intente recordar hechos sobre matemáticas, biología o historia, dejamos que cite el material de la propia institución.

const chunks = await retrieve({
  query: reformulatedQuery,         // 1. reformula la query con contexto
  filters: { tenantId, courseId },  // 2. aislamiento absoluto
  k: 10,
  tenantOnlyMode: true,             // 3. nunca cita contenido de otra institución
  boostByWeakAreas: snapshot.quizContext.weakAreas,  // 4. prioriza chunks de áreas débiles
});

El RAG per-tenant es crítico. El preuniversitario XYZ tiene material propio sobre el examen de ingreso. La Universidad ABC tiene material propio sobre Cálculo. El tutor cita el material CORRECTO de la institución, no un agregado genérico.

Cada chunk tiene metadata: { source: "course_lesson", courseId, lessonId, lessonTitle, moduleTitle }. Cuando el tutor responde, cita: "Como se explicó en la clase 'Geometría Analítica' del módulo 3..."

Voyage AI genera los embeddings (1024 dimensiones, fallback OpenAI). pgvector almacena. tenantOnlyMode: true garantiza que WHERE tenantId = X está siempre en la query. Regla crítica del proyecto: cero leakage cross-tenant.

3. PedagogicalAgent — adaptación de estrategia

Determinismo puro. Evalúa el mastery del alumno en el dominio específico y selecciona una de 5 estrategias:

MasteryEstrategiaComportamiento
< 0.3direct_instructionExplicación clara, ejemplos concretos, paso a paso
0.3-0.5scaffoldingPistas progresivas, preguntas guiadas simples
0.5-0.7socraticPreguntas que llevan al descubrimiento
0.7-0.9guided_practiceEjercicios con feedback, aplicación práctica
> 0.9challengeProblemas complejos, conexiones entre conceptos

Ajustes adicionales por divergencia entre quiz y chat:

  • Mastery alta en el chat + quiz bajo → "comprensión superficial" → nudge DOWN
  • Mastery baja + quiz alto → "alumno callado" → nudge UP
  • Quiz pass rate < 40% → cap en scaffolding (todavía no avanza a socratic)

También ajusta por edad (User.isMinor), estilo de aprendizaje, dominio (matemáticas vs literatura tienen perfiles distintos).

Cero costo LLM. Output: estrategia seleccionada + instrucciones específicas para agregar al system prompt.

4. Orchestrator — buildEnrichedPrompt

Construye el system prompt enriquecido:

Eres un tutor IA para el curso "Cálculo I" de la institución "Preuniversitario XYZ".

DOMINIO DEL ALUMNO:
- Límites: mastery 0.78 (alto)
- Derivadas: mastery 0.42 (medio)
- Integrales: mastery 0.15 (bajo)

MISCONCEPTIONS ACTIVAS:
- "El alumno confunde dominio con imagen en funciones" (3 ocurrencias, estado: resolving)
- "El alumno aplica la derivada de la suma al producto" (5 ocurrencias, estado: active)

RENDIMIENTO EN QUIZ:
- 14 intentos en total, avgScore 67%, passRate 71%
- Áreas débiles: integrales (prom. 45%), regla de la cadena (prom. 52%)

ESTRATEGIA PEDAGÓGICA: guided_practice
- El alumno tiene dominio medio en derivadas. Presenta ejercicios graduales.
- Refuerza la conexión entre límites y derivadas (ya domina los límites).
- Aborda proactivamente el misconception sobre la derivada de un producto.

CONTEXTO RAG (del material del curso):
[Clase 3.2 "Regla del Producto"] (Módulo: Cálculo Diferencial)
"La derivada de f(x).g(x) NO es f'(x).g'(x). La regla correcta es..."

[Clase 3.5 "Ejercicios Resueltos"] (Módulo: Cálculo Diferencial)
"Ejemplo: derivar (x^2 + 1).(x - 3) usando la regla del producto..."

QUIZ RECIENTE (próxima conversación):
El alumno acaba de responder un quiz inline con 2 preguntas, acertó 1.

INSTRUCCIONES:
- Responde en español
- Cita el material del curso cuando sea relevante (usa [Clase X.Y])
- Reconoce lo que el alumno acertó antes de señalar el error
- Para esta edad (User.ageRange = "young_adult"): lenguaje casual sin ser demasiado informal

5. LLM principal — streaming con 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 al cliente
}

El LLM Router hace:

  1. Resuelve el proveedor via TenantTaskModelConfig (el admin eligió Claude Sonnet, GPT-4o, etc.)
  2. Resuelve la API key via cascada: TenantApiKey → ProviderApiKey global → process.env
  3. Circuit breaker check (estado en Redis). Si el proveedor está en OPEN: salta directo al fallback
  4. Middleware de metering: rate limit + credit check + cost calculator
  5. Stream con Vercel AI SDK (soporta tools, multimodal, structured output)
  6. En caso de error: fallback automático al siguiente proveedor en la cadena

Cadena de fallback por tier:

Tier Sonnet (medio): Claude Sonnet → GPT-4o → Grok-3-fast → Gemini Pro
Tier Haiku (rápido): Claude Haiku → GPT-4o-mini → Grok-3-mini → Gemini Flash
Tier Opus (complejo): Claude Opus → GPT-4.5 → Grok-3 → Gemini 2.5 Pro

El tenant NUNCA se queda sin tutor. Si Anthropic cae → OpenAI toma el relevo. Si OpenAI también cae → xAI. Etc.

6. EvaluationAgent — feedback loop

En background después de la respuesta del tutor:

after(async () => {
  const evaluation = await router.generateDirect({
    taskType: "chat_evaluation",
    messages: [
      { role: "user", content: "El alumno dijo: '...'. El tutor respondió: '...'. Clasifica." }
    ]
  });

  // evaluation: {
  //   understanding: "partial",
  //   detectedMisconceptions: [{ description, concepts, severity }],
  //   suggestedNextStep: "..."
  // }

  // Actualiza ConceptMastery via Bayesian update
  await conceptMasteryEngine.updateFromTurn({
    userId, courseId, evaluation
  });

  // Persiste/actualiza misconceptions
  for (const misc of evaluation.detectedMisconceptions) {
    await misconceptionResolutionService.upsert({
      userId, source: "chat", ...misc
    });
  }
});

Costo: ~$0.001 por turno (Haiku). NO bloquea el request del alumno.

Los misconceptions tienen un ciclo de vida de 3 estados: active → resolving → resolved. La máquina de estados determina las transiciones basándose en evidencia (actualización de mastery, aprobación de quiz, abordaje explícito por parte del tutor).

7. ContentAgent — pre-generación proactiva

after(async () => {
  // El alumno demuestra dominio débil en el concepto X
  // Pre-genera un ejercicio de seguimiento mientras el alumno lee la respuesta actual
  const exercise = await router.generateDirect({
    taskType: "content_generation",
    messages: [...]
  });

  // Cache Redis 30min
  await redis.set(`next-exercise:${userId}:${conceptId}`, exercise, 1800);
});

Cuando el alumno termina de leer la respuesta y dice "dame un ejercicio", Studeia lo sirve INSTANTÁNEAMENTE desde el cache. Sin latencia perceptible.

Costo: ~$0.001 por turno (Haiku).

8. SupervisorAgent — moderación

Corre en background después de cada turno. Clasifica en 5 niveles de severidad x 8 categorías.

Categorías: lenguaje inapropiado, violencia, ilegal, sexual, off_topic, harassment, self_harm, jailbreak_attempt.

Severidad: low → medium → high → critical → safety.

3 strikes (LOW/MEDIUM en 7 días) = cuarentena 48h. CRITICAL = cuarentena 7 días.

Self-harm (severity=safety) NUNCA penaliza al alumno. En cambio:

  • El tutor se interrumpe con un mensaje de acogida
  • Recursos de crisis (líneas de ayuda locales según el país del tenant)
  • Cooldown Redis 24h (no cuarentena)
  • Email URGENTE inmediato al admin institucional

Filosofía: self-harm es una crisis, no una infracción. Detalles en Safety Supervisor.

Costo: ~$0.001 por turno (Haiku).

Números en producción

Después de 6 meses en producción:

  • ~30 ms de latencia adicional de los agentes pre-LLM
  • ~$0.005-$0.05 costo promedio por turno
  • 91% tasa de retención de alumnos después de 7 días (vs ~40% benchmark de tutor IA sin estado)
  • 3.2x tasa de detección de misconceptions vs baseline de llamada única
  • 0 incidentes graves de safety (categorías high/critical)

Trade-offs honestos

Cosas que NO funcionaron:

  • Intentamos un "MasterAgent" coordinador via LLM para elegir el siguiente agente dinámicamente. El costo se duplicó, la latencia subió 800ms, la calidad NO mejoró. Volvimos al determinismo en el Orchestrator.

  • Intentamos hacer FineTune de Llama con material de cursos. Caro para cada tenant. RAG funciona mejor para knowledge dinámico (la institución actualiza el material cada semana — el fine-tune quedaría stale).

  • Intentamos "consensus" entre 3 LLMs (Claude + GPT + Gemini) y tomar la respuesta con mayoría. Costo 3x sin ganancia significativa de calidad. Lo eliminamos — la cadena de fallback es suficiente.

¿Código abierto?

Estamos evaluando hacer open-source de los componentes determinísticos (StudentModelService, RetrievalAgent, PedagogicalAgent) como paquete npm. Los agentes LLM-driven (Evaluation, Content, Supervisor) tienen prompts que son IP de Studeia y permanecerán cerrados.

Si te interesa: abre un issue en github.com/donattocosta-lang/studeia/issues.

Ver también

FAQ

¿Por qué multi-agente y no una sola llamada LLM?

Una sola llamada LLM NO tiene memoria persistente del alumno, NO sabe qué materiales del curso citar (necesita RAG), NO modera salidas inadecuadas y NO actualiza el modelo bayesiano del dominio del alumno. El enfoque multi-agente resuelve cada problema con un agente especializado: StudentModel mantiene el estado, RetrievalAgent busca RAG tenant-scoped, PedagogicalAgent elige la estrategia, EvaluationAgent clasifica misconceptions, ContentAgent pre-genera el seguimiento, SupervisorAgent modera. Cada uno puede optimizarse de forma independiente.

¿Cuánto cuesta por turno completo del pipeline?

Típicamente $0.005-$0.05 por turno (depende del tamaño de la respuesta). Desglose: LLM principal Sonnet $0.005-$0.04 + EvaluationAgent Haiku $0.001 + ContentAgent Haiku $0.001 + SupervisorAgent Haiku $0.001. Componentes determinísticos en TypeScript, como StudentModel, RetrievalAgent y PedagogicalAgent, tienen cero costo LLM.

¿Cómo se evita que el costo explote con 1000+ alumnos?

Cuatro mecanismos: (1) Agentes en background via Next.js `after()` — sin bloquear el request. (2) Haiku para tareas en background (~30x más barato que Sonnet). (3) Middleware de metering con rate limit por usuario. (4) El tenant puede traer su propia API key (TenantApiKey) — Studeia no cobra margen de IA, los costos van directamente a la cuenta del tenant en Anthropic/OpenAI.

Veja tambem

Cómo construimos un pipeline multi-agente de tutor IA en EAD