--- id: "ET-LLM-004" title: "Integración con Módulo Educativo" type: "Technical Specification" status: "Done" priority: "Alta" epic: "OQI-007" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # ET-LLM-004: Integración con Módulo Educativo **Épica:** OQI-007 - LLM Strategy Agent **Versión:** 1.0 **Fecha:** 2025-12-05 **Estado:** Planificado **Prioridad:** P1 - Alto --- ## Resumen Esta especificación define cómo el agente LLM se integra con el módulo educativo (OQI-002) para proporcionar asistencia personalizada, explicaciones contextuales y recomendaciones de aprendizaje. --- ## Arquitectura de Integración ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ EDUCATION INTEGRATION LAYER │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────────────┐ ┌────────────────────┐ │ │ │ OQI-002 │ │ OQI-007 │ │ │ │ Education Module │ │ LLM Agent │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ │ │ │ Courses │◄─┼─────┼─►│ Education │ │ │ │ │ │ Service │ │ │ │ Assistant │ │ │ │ │ └──────────────┘ │ │ │ Agent │ │ │ │ │ │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ │ │ │ │ │ Progress │◄─┼─────┼─────────┘ │ │ │ │ │ Tracker │ │ │ │ │ │ │ └──────────────┘ │ │ ┌──────────────┐ │ │ │ │ │ │ │ Learning │ │ │ │ │ ┌──────────────┐ │ │ │ Recommender │ │ │ │ │ │ Content │◄─┼─────┼─►│ │ │ │ │ │ │ Library │ │ │ └──────────────┘ │ │ │ │ └──────────────┘ │ │ │ │ │ └────────────────────┘ └────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘ ``` --- ## Componentes ### 1. Education Context Builder ```typescript // src/modules/copilot/education/context-builder.ts interface EducationContext { currentCourse: CourseInfo | null; currentLesson: LessonInfo | null; overallProgress: ProgressSummary; recentQuizzes: QuizResult[]; strugglingTopics: Topic[]; masteredTopics: Topic[]; suggestedNextSteps: Suggestion[]; } interface CourseInfo { id: string; title: string; level: 'beginner' | 'intermediate' | 'advanced'; progress: number; currentModule: string; } interface LessonInfo { id: string; title: string; type: 'video' | 'reading' | 'interactive'; concepts: string[]; duration: number; completed: boolean; } @Injectable() export class EducationContextBuilder { constructor( private readonly courseService: CourseService, private readonly progressService: ProgressService, ) {} async build(userId: string): Promise { const [ enrollments, progress, quizResults, analytics, ] = await Promise.all([ this.courseService.getUserEnrollments(userId), this.progressService.getUserProgress(userId), this.courseService.getRecentQuizzes(userId, 10), this.progressService.getLearningAnalytics(userId), ]); // Find current course (most recently accessed) const currentEnrollment = enrollments .filter(e => e.status === 'in_progress') .sort((a, b) => b.lastAccessedAt.getTime() - a.lastAccessedAt.getTime())[0]; let currentCourse: CourseInfo | null = null; let currentLesson: LessonInfo | null = null; if (currentEnrollment) { const course = await this.courseService.getCourse(currentEnrollment.courseId); const lesson = await this.courseService.getCurrentLesson( userId, currentEnrollment.courseId, ); currentCourse = { id: course.id, title: course.title, level: course.level, progress: currentEnrollment.progress, currentModule: lesson?.moduleName || 'N/A', }; if (lesson) { currentLesson = { id: lesson.id, title: lesson.title, type: lesson.type, concepts: lesson.concepts, duration: lesson.duration, completed: lesson.completed, }; } } // Analyze struggling topics const strugglingTopics = this.identifyStrugglingTopics(quizResults, analytics); const masteredTopics = this.identifyMasteredTopics(quizResults, analytics); // Generate suggestions const suggestedNextSteps = await this.generateSuggestions( userId, strugglingTopics, progress, ); return { currentCourse, currentLesson, overallProgress: progress.summary, recentQuizzes: quizResults.slice(0, 5), strugglingTopics, masteredTopics, suggestedNextSteps, }; } private identifyStrugglingTopics( quizResults: QuizResult[], analytics: LearningAnalytics, ): Topic[] { const topicScores = new Map(); // Aggregate quiz scores by topic for (const result of quizResults) { for (const topic of result.topics) { const scores = topicScores.get(topic) || []; scores.push(result.topicScores[topic] || result.score); topicScores.set(topic, scores); } } // Identify topics with consistently low scores const struggling: Topic[] = []; for (const [topic, scores] of topicScores) { const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length; if (avgScore < 70 && scores.length >= 2) { struggling.push({ name: topic, averageScore: avgScore, attempts: scores.length, lastAttempt: this.findLastAttemptDate(quizResults, topic), }); } } return struggling.sort((a, b) => a.averageScore - b.averageScore); } private async generateSuggestions( userId: string, strugglingTopics: Topic[], progress: UserProgress, ): Promise { const suggestions: Suggestion[] = []; // Suggest review for struggling topics for (const topic of strugglingTopics.slice(0, 3)) { const relatedLesson = await this.courseService.findLessonByTopic(topic.name); if (relatedLesson) { suggestions.push({ type: 'review', priority: 'high', title: `Revisar: ${topic.name}`, description: `Tu puntuación promedio es ${topic.averageScore}%. Te recomiendo repasar este tema.`, action: { type: 'goto_lesson', lessonId: relatedLesson.id, lessonTitle: relatedLesson.title, }, }); } } // Suggest next lesson if current course in progress const nextLesson = await this.progressService.getNextLesson(userId); if (nextLesson) { suggestions.push({ type: 'continue', priority: 'medium', title: `Continuar: ${nextLesson.title}`, description: `Estás en el ${progress.summary.currentCourseProgress}% del curso.`, action: { type: 'goto_lesson', lessonId: nextLesson.id, lessonTitle: nextLesson.title, }, }); } // Suggest practice if no recent activity if (progress.summary.daysSinceLastActivity > 3) { suggestions.push({ type: 'practice', priority: 'medium', title: 'Práctica recomendada', description: 'Han pasado algunos días. ¿Qué tal un poco de práctica?', action: { type: 'start_practice', topics: strugglingTopics.map(t => t.name), }, }); } return suggestions; } } ``` ### 2. Education Assistant Agent ```typescript // src/modules/copilot/education/assistant-agent.ts const EDUCATION_SYSTEM_PROMPT = ` # Education Assistant - Trading Platform Academy ## Tu Rol Eres un tutor personalizado de trading, especializado en ayudar a los usuarios a aprender conceptos de trading e inversión. Tu objetivo es hacer que el aprendizaje sea accesible, interesante y aplicable al mundo real. ## Contexto del Usuario {user_education_context} ## Principios de Enseñanza 1. **Adapta al nivel:** Ajusta la complejidad de tus explicaciones al nivel del usuario 2. **Usa ejemplos reales:** Conecta conceptos con situaciones de mercado actuales 3. **Fomenta la práctica:** Sugiere ejercicios en paper trading 4. **Sé paciente:** Si el usuario no entiende, intenta explicar de forma diferente 5. **Celebra el progreso:** Reconoce los logros del usuario ## Reglas Importantes 1. NUNCA des respuestas directas a quizzes o evaluaciones 2. Si detectas confusión persistente, sugiere revisar lecciones específicas 3. Siempre relaciona la teoría con la práctica 4. No hagas promesas de rentabilidad 5. Usa analogías simples para conceptos complejos 6. Si no conoces un tema específico del curso, admítelo ## Herramientas Disponibles - get_lesson_content(lessonId): Obtener contenido de una lección - get_user_progress(): Ver progreso del usuario - find_related_lessons(topic): Buscar lecciones relacionadas a un tema - get_practice_questions(topic): Obtener preguntas de práctica - get_market_example(concept): Buscar ejemplo real en mercado actual ## Formato de Respuesta para Explicaciones ### Explicación de Concepto 1. Definición simple (1-2 oraciones) 2. Analogía o metáfora 3. Ejemplo con mercado real 4. Cómo aplicarlo en trading 5. Recurso adicional (lección relacionada) ### Asistencia en Quiz (sin dar respuestas) 1. Clarificar qué se pregunta 2. Recordar conceptos relevantes 3. Dar pista indirecta 4. Sugerir revisar sección específica `; @Injectable() export class EducationAssistantAgent { constructor( private readonly llmProvider: LLMProviderService, private readonly contextBuilder: EducationContextBuilder, private readonly lessonService: LessonService, private readonly marketDataService: MarketDataService, ) {} async processEducationQuery( userId: string, query: string, conversationHistory: Message[], ): Promise> { // Build education context const educationContext = await this.contextBuilder.build(userId); // Format context for system prompt const formattedContext = this.formatEducationContext(educationContext); // Detect query intent const intent = await this.detectIntent(query); // Build tools based on intent const tools = this.getToolsForIntent(intent); // Create completion const systemPrompt = EDUCATION_SYSTEM_PROMPT.replace( '{user_education_context}', formattedContext, ); return this.llmProvider.createChatCompletion({ model: 'gpt-4o', messages: [ { role: 'system', content: systemPrompt }, ...conversationHistory, { role: 'user', content: query }, ], tools, stream: true, }); } private formatEducationContext(context: EducationContext): string { let formatted = ''; if (context.currentCourse) { formatted += `## Curso Actual\n`; formatted += `- **Curso:** ${context.currentCourse.title}\n`; formatted += `- **Nivel:** ${context.currentCourse.level}\n`; formatted += `- **Progreso:** ${context.currentCourse.progress}%\n`; formatted += `- **Módulo actual:** ${context.currentCourse.currentModule}\n\n`; } if (context.currentLesson) { formatted += `## Lección Actual\n`; formatted += `- **Lección:** ${context.currentLesson.title}\n`; formatted += `- **Conceptos:** ${context.currentLesson.concepts.join(', ')}\n`; formatted += `- **Estado:** ${context.currentLesson.completed ? 'Completada' : 'En progreso'}\n\n`; } if (context.strugglingTopics.length > 0) { formatted += `## Temas con Dificultad\n`; for (const topic of context.strugglingTopics) { formatted += `- ${topic.name} (promedio: ${topic.averageScore}%)\n`; } formatted += '\n'; } if (context.masteredTopics.length > 0) { formatted += `## Temas Dominados\n`; formatted += context.masteredTopics.map(t => t.name).join(', ') + '\n\n'; } if (context.suggestedNextSteps.length > 0) { formatted += `## Sugerencias de Siguiente Paso\n`; for (const suggestion of context.suggestedNextSteps) { formatted += `- [${suggestion.priority}] ${suggestion.title}\n`; } } return formatted; } private async detectIntent(query: string): Promise { // Simple keyword-based intent detection const lowerQuery = query.toLowerCase(); if (lowerQuery.includes('quiz') || lowerQuery.includes('examen') || lowerQuery.includes('respuesta')) { return 'quiz_help'; } if (lowerQuery.includes('qué es') || lowerQuery.includes('explica') || lowerQuery.includes('cómo funciona')) { return 'concept_explanation'; } if (lowerQuery.includes('ejemplo') || lowerQuery.includes('práctica')) { return 'practical_example'; } if (lowerQuery.includes('siguiente') || lowerQuery.includes('qué debo')) { return 'learning_path'; } return 'general_help'; } private getToolsForIntent(intent: EducationIntent): Tool[] { const baseTools = [ { type: 'function', function: { name: 'get_lesson_content', description: 'Obtiene el contenido de una lección específica', parameters: { type: 'object', properties: { lessonId: { type: 'string' }, }, required: ['lessonId'], }, }, }, { type: 'function', function: { name: 'find_related_lessons', description: 'Busca lecciones relacionadas a un tema', parameters: { type: 'object', properties: { topic: { type: 'string' }, }, required: ['topic'], }, }, }, ]; if (intent === 'practical_example') { baseTools.push({ type: 'function', function: { name: 'get_market_example', description: 'Busca un ejemplo real en el mercado actual', parameters: { type: 'object', properties: { concept: { type: 'string' }, }, required: ['concept'], }, }, }); } if (intent === 'quiz_help') { baseTools.push({ type: 'function', function: { name: 'get_practice_questions', description: 'Obtiene preguntas de práctica (no del quiz oficial)', parameters: { type: 'object', properties: { topic: { type: 'string' }, difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'] }, }, required: ['topic'], }, }, }); } return baseTools; } } ``` ### 3. Learning Recommender ```typescript // src/modules/copilot/education/learning-recommender.ts interface LearningRecommendation { type: 'course' | 'lesson' | 'practice' | 'review'; item: CourseItem | LessonItem | PracticeItem; reason: string; priority: number; estimatedTime: number; // minutes } @Injectable() export class LearningRecommender { constructor( private readonly courseService: CourseService, private readonly progressService: ProgressService, private readonly analyticsService: LearningAnalyticsService, ) {} async getRecommendations( userId: string, limit: number = 5, ): Promise { const [ progress, analytics, availableCourses, completedCourses, ] = await Promise.all([ this.progressService.getUserProgress(userId), this.analyticsService.getUserAnalytics(userId), this.courseService.getAvailableCourses(userId), this.courseService.getCompletedCourses(userId), ]); const recommendations: LearningRecommendation[] = []; // 1. Continue current course if (progress.currentCourse) { const nextLesson = await this.progressService.getNextLesson(userId); if (nextLesson) { recommendations.push({ type: 'lesson', item: nextLesson, reason: 'Continúa donde lo dejaste', priority: 100, estimatedTime: nextLesson.duration, }); } } // 2. Review struggling topics const strugglingTopics = analytics.strugglingTopics || []; for (const topic of strugglingTopics.slice(0, 2)) { const reviewLesson = await this.courseService.findReviewMaterial(topic.name); if (reviewLesson) { recommendations.push({ type: 'review', item: reviewLesson, reason: `Refuerza tu comprensión de ${topic.name}`, priority: 90 - topic.averageScore, estimatedTime: 15, }); } } // 3. Practice recommendations if (analytics.daysSinceLastPractice > 2) { const practiceTopics = this.selectPracticeTopics(analytics); recommendations.push({ type: 'practice', item: { topics: practiceTopics, questionCount: 10, }, reason: 'La práctica hace al maestro', priority: 70, estimatedTime: 20, }); } // 4. Next course recommendation if (progress.currentCourse?.progress > 80) { const nextCourse = this.selectNextCourse( completedCourses, availableCourses, analytics, ); if (nextCourse) { recommendations.push({ type: 'course', item: nextCourse, reason: 'Siguiente paso en tu aprendizaje', priority: 50, estimatedTime: nextCourse.totalDuration, }); } } // Sort by priority and limit return recommendations .sort((a, b) => b.priority - a.priority) .slice(0, limit); } private selectPracticeTopics(analytics: LearningAnalytics): string[] { const topics: string[] = []; // Mix of struggling and mastered topics (70/30 ratio) const struggling = (analytics.strugglingTopics || []).slice(0, 3); const mastered = (analytics.masteredTopics || []).slice(0, 1); topics.push(...struggling.map(t => t.name)); topics.push(...mastered.map(t => t.name)); return topics; } private selectNextCourse( completed: Course[], available: Course[], analytics: LearningAnalytics, ): Course | null { // Filter out completed courses const completedIds = new Set(completed.map(c => c.id)); const candidates = available.filter(c => !completedIds.has(c.id)); if (candidates.length === 0) return null; // Score candidates based on prerequisites and user level const scored = candidates.map(course => { let score = 0; // Check prerequisites const prereqsMet = course.prerequisites.every(p => completedIds.has(p)); if (!prereqsMet) return { course, score: -1 }; // Score based on level progression const levelOrder = ['beginner', 'intermediate', 'advanced']; const userLevel = analytics.estimatedLevel || 'beginner'; const courseLevel = course.level; const userLevelIndex = levelOrder.indexOf(userLevel); const courseLevelIndex = levelOrder.indexOf(courseLevel); if (courseLevelIndex === userLevelIndex) score += 50; if (courseLevelIndex === userLevelIndex + 1) score += 30; // Score based on topic interest const userInterests = analytics.topicInterests || []; const matchingTopics = course.topics.filter(t => userInterests.includes(t) ).length; score += matchingTopics * 10; return { course, score }; }); const best = scored .filter(s => s.score >= 0) .sort((a, b) => b.score - a.score)[0]; return best?.course || null; } } ``` --- ## Tools para el Agente ### get_lesson_content ```typescript async function getLessonContent(lessonId: string): Promise { const lesson = await lessonService.getLesson(lessonId); return { id: lesson.id, title: lesson.title, summary: lesson.summary, keyPoints: lesson.keyPoints, concepts: lesson.concepts, examples: lesson.examples, relatedLessons: lesson.relatedLessons.map(l => ({ id: l.id, title: l.title, })), }; } ``` ### get_market_example ```typescript async function getMarketExample(concept: string): Promise { // Map concepts to market patterns const conceptPatterns: Record = { 'soporte': ['support_bounce', 'support_break'], 'resistencia': ['resistance_rejection', 'breakout'], 'rsi': ['oversold_bounce', 'overbought_reversal'], 'macd': ['bullish_crossover', 'bearish_crossover'], 'tendencia': ['uptrend', 'downtrend'], }; const patterns = conceptPatterns[concept.toLowerCase()] || []; // Find recent example in market const example = await marketDataService.findPatternExample(patterns, { lookbackDays: 30, preferLiquid: true, }); if (!example) { return { found: false, message: `No encontré un ejemplo reciente de ${concept} en el mercado.`, }; } return { found: true, symbol: example.symbol, pattern: example.pattern, date: example.date, description: example.description, chartUrl: `/charts/${example.symbol}?highlight=${example.date}`, }; } ``` --- ## Dependencias ### Módulos Internos - CourseService (OQI-002) - ProgressService (OQI-002) - LessonService (OQI-002) - MarketDataService (OQI-003) ### Base de Datos - courses - lessons - enrollments - lesson_progress - quiz_results --- ## Referencias - [RF-LLM-004: Asistencia Educativa](../requerimientos/RF-LLM-004-educational-assistance.md) - [OQI-002: Módulo Educativo](../../OQI-002-education/) --- *Especificación técnica - Sistema NEXUS* *Trading Platform*