Arquitectura del AI Backend
Árbol de directorios
Section titled “Árbol de directorios”apps/ai-backend/src/├── main.ts ← Arranca en puerto 3001├── app.module.ts ← Módulo raíz├── common/│ ├── enums/│ │ └── ai.enums.ts ← AIProvider, AIUseCase│ └── guards/│ └── internal-auth.guard.ts ← Valida x-internal-key└── modules/ ├── credentials/ ← Gestión de API keys cifradas ├── providers/ ← Adapters LLM (Anthropic/OpenAI/Gemini) ├── router/ ← Enrutamiento + circuit breaker ├── vector-db/ ← Qdrant + embeddings ├── metrics/ ← Registro de uso y costes ├── health/ ← Health check de proveedores ├── admin/ ← Endpoints de administración └── usecases/ ├── tutor/ ← Tutor conversacional ├── adaptive/ ← Motor adaptativo ├── fluency-test/ ← Scoring del test de fluencia ├── content-gen/ ← Generación de quizzes/resúmenes/casos ├── evaluation/ ← Evaluación de respuestas abiertas ├── churn/ ← Predicción de abandono └── indexing/ ← Indexación en QdrantMódulos NestJS del microservicio
Section titled “Módulos NestJS del microservicio”| Módulo | Responsabilidad |
|---|---|
CredentialsModule | Cifrado AES-256-GCM y caché de API keys |
ProvidersModule | Los 3 adapters LLM con interfaz común |
RouterModule | Resolución de proveedor/modelo por caso de uso + circuit breaker |
VectorDbModule | QdrantService + EmbeddingService + IndexingService |
MetricsModule | Registro de AIUsageMetric con estimación de coste |
HealthModule | Ping a los 3 proveedores + estado de Qdrant |
AdminModule | Endpoints /admin/metrics, /admin/router, /admin/vector-db |
TutorModule | POST /usecases/tutor/chat con streaming SSE |
AdaptiveModule | POST /usecases/adaptive/recommend |
FluencyTestModule | POST /usecases/fluency-test/score |
ContentGenModule | `POST /usecases/content-gen/quiz |
EvaluationModule | POST /usecases/evaluation/grade |
ChurnModule | POST /usecases/churn/predict |
IndexingModule | POST /usecases/indexing/index-lesson |
InternalAuthGuard
Section titled “InternalAuthGuard”Todos los endpoints del AI Backend están protegidos con:
@Injectable()export class InternalAuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const key = request.headers['x-internal-key']; if (key !== process.env.AIBACKEND_API_KEY) { throw new UnauthorizedException('Invalid internal API key'); } return true; }}El secret AIBACKEND_API_KEY debe estar en el .env de ambas apps (api y ai-backend).
Flujo de una request completa
Section titled “Flujo de una request completa”sequenceDiagram participant W as apps/web participant A as apps/api participant AI as apps/ai-backend participant P as LLM Provider participant Q as Qdrant
W->>A: POST /tutor/chat A->>AI: POST /usecases/tutor/chat (x-internal-key) AI->>AI: InternalAuthGuard valida key AI->>AI: RouterService.resolveProvider(TUTOR_CHAT) AI->>AI: CircuitBreaker.isOpen(anthropic) → false AI->>AI: CredentialsService.getKey(anthropic) → API key descifrada AI->>Q: Buscar contexto relevante para el usuario Q-->>AI: Top-K chunks similares AI->>P: Anthropic.messages.stream(...) P-->>AI: Tokens en streaming AI-->>A: SSE chunks A-->>W: SSE chunks AI->>AI: MetricsService.record(useCase, tokens, latency, cost)Inicialización del servicio
Section titled “Inicialización del servicio”Al arrancar, RouterService.onModuleInit() carga los AIRouteConfig desde apps/api:
async onModuleInit(): Promise<void> { await this.loadRoutesFromApi(); // GET /internal/ai-routes}Si la API no está disponible, usa DEFAULT_ROUTE_CONFIG (hardcoded) como fallback.