Habilidades target → Prompt Engineering → LLM (Claude/GPT-4)
↓
Problema + Rúbrica (JSON)
↓
Validación humana
↓
Almacenamiento en BD
PROBLEM_GENERATION_PROMPT_TEMPLATE = """
Eres un experto en diseño de problemas educativos para Formación Profesional en la especialidad de Comercio y Marketing.
# CONTEXTO
- Módulo: Técnicas Básicas de Merchandising
- Nivel: Ciclo Formativo de Grado Medio
- Edad estudiantes: 16-20 años
# TAREA
Genera un problema práctico que permita evaluar las siguientes habilidades procedimentales:
{skill_descriptions}
# REQUISITOS DEL PROBLEMA
1. **Contexto realista:** Situación que un técnico en comercio enfrentaría en el mundo real
2. **Secuencia clara:** El problema debe requerir varios pasos de resolución
3. **Evaluable:** Cada habilidad debe ser observable en la respuesta del estudiante
4. **Nivel de dificultad:** {difficulty_level}
5. **Tiempo estimado:** {estimated_minutes} minutos
# FORMATO DE SALIDA (JSON)
Devuelve ÚNICAMENTE un JSON válido con esta estructura:
{{
"title": "Título corto y descriptivo",
"description": "Enunciado completo en Markdown (incluye contexto, datos, tareas)",
"required_skills": ["H3.1.1", "H3.1.3", ...],
"difficulty": "easy|medium|hard",
"estimated_time_minutes": 30,
"rubric": {{
"criteria": [
{{
"skill_id": "H3.1.1",
"criterion_name": "Identificación del público objetivo",
"max_points": 10,
"levels": [
{{"score": 10, "description": "Identifica correctamente edad, estilo de vida y motivaciones con justificación sólida"}},
{{"score": 7, "description": "Identifica correctamente 2 de 3 aspectos"}},
{{"score": 4, "description": "Identificación parcial o genérica"}},
{{"score": 0, "description": "No identifica el público objetivo"}}
]
}},
...
]
}},
"solution_example": "Ejemplo de respuesta correcta (para validación)",
"common_errors": ["Error típico 1", "Error típico 2"]
}}
# EJEMPLOS DE PROBLEMAS SIMILARES
{few_shot_examples}
# COMIENZA LA GENERACIÓN
"""
# Few-shot examples
FEW_SHOT_EXAMPLES = """
## Ejemplo 1: Análisis de circulación en tienda
**Habilidades:** H1.2 (Analizar circulación), H1.3 (Identificar zonas calientes/frías)
**Problema:**
"Dado el plano de una tienda de ropa de 200m² con 3 entradas, analiza el flujo de clientes y propón la ubicación óptima para: (1) zona de cajas, (2) productos de impulso, (3) probadores..."
## Ejemplo 2: Selección cromática para escaparate
**Habilidades:** H3.3.1 (Paleta cromática), H3.3.2 (Psicología del color)
**Problema:**
"Una marca de cosmética natural quiere transmitir valores de sostenibilidad y frescura. Propón una paleta cromática principal y secundaria justificando tu elección según principios de psicología del color..."
"""
PROBLEM_VALIDATION_PROMPT = """
Evalúa si el siguiente problema cumple con los criterios de calidad:
# PROBLEMA GENERADO
{generated_problem}
# CRITERIOS DE VALIDACIÓN
1. **Claridad:** ¿El enunciado es comprensible para un estudiante de 16-20 años?
2. **Realismo:** ¿La situación es creíble y profesional?
3. **Alineación:** ¿El problema realmente evalúa las habilidades indicadas?
4. **Rúbrica:** ¿La rúbrica es específica y aplicable?
5. **Originalidad:** ¿No es un copia literal de ejemplos conocidos?
# RESPUESTA (JSON)
{{
"is_valid": true|false,
"score_clarity": 1-5,
"score_realism": 1-5,
"score_alignment": 1-5,
"score_rubric_quality": 1-5,
"issues_found": ["Problema 1", "Problema 2", ...],
"suggestions": ["Sugerencia de mejora 1", ...]
}}
"""
import anthropic
import json
from typing import List
class LLMProblemGenerator:
def __init__(self, api_key: str, model: str = "claude-3-5-sonnet-20241022"):
self.client = anthropic.Anthropic(api_key=api_key)
self.model = model
def generate_problem(
self,
target_skills: List[Skill],
difficulty: ProblemDifficulty = ProblemDifficulty.MEDIUM,
estimated_minutes: int = 30
) -> Problem:
"""Genera un problema usando Claude"""
# Preparar descripciones de habilidades
skill_descriptions = "\n".join([
f"- **{skill.id}:** {skill.name}\n {skill.description}"
for skill in target_skills
])
# Construir prompt
prompt = PROBLEM_GENERATION_PROMPT_TEMPLATE.format(
skill_descriptions=skill_descriptions,
difficulty_level=difficulty.value,
estimated_minutes=estimated_minutes,
few_shot_examples=FEW_SHOT_EXAMPLES
)
# Llamar a Claude
response = self.client.messages.create(
model=self.model,
max_tokens=4000,
temperature=0.7, # Creatividad moderada
messages=[
{"role": "user", "content": prompt}
]
)
# Parsear respuesta JSON
try:
problem_data = json.loads(response.content[0].text)
except json.JSONDecodeError:
# Si Claude devuelve Markdown code block, extraer
text = response.content[0].text
json_match = re.search(r'```json\n(.*?)\n```', text, re.DOTALL)
if json_match:
problem_data = json.loads(json_match.group(1))
else:
raise ValueError("No se pudo parsear la respuesta del LLM")
# Crear objeto Problem
problem = Problem(
id=f"prob_{uuid.uuid4().hex[:8]}",
title=problem_data['title'],
description=problem_data['description'],
required_skills=[skill.id for skill in target_skills],
primary_skill=target_skills[0].id,
difficulty=difficulty,
estimated_time_minutes=problem_data['estimated_time_minutes'],
rubric=problem_data['rubric'],
created_by=self.model,
created_at=datetime.now(),
generation_prompt=prompt,
llm_model=self.model
)
return problem
def validate_problem(self, problem: Problem) -> Dict:
"""Valida automáticamente un problema generado"""
validation_prompt = PROBLEM_VALIDATION_PROMPT.format(
generated_problem=json.dumps(problem.dict(), indent=2)
)
response = self.client.messages.create(
model=self.model,
max_tokens=1000,
temperature=0.3, # Más determinista para validación
messages=[
{"role": "user", "content": validation_prompt}
]
)
validation_result = json.loads(response.content[0].text)
return validation_result
def batch_generate(
self,
skill_combinations: List[List[Skill]],
problems_per_combination: int = 3
) -> List[Problem]:
"""Genera lotes de problemas"""
all_problems = []
for skills in skill_combinations:
for difficulty in [ProblemDifficulty.EASY, ProblemDifficulty.MEDIUM, ProblemDifficulty.HARD]:
for _ in range(problems_per_combination):
try:
problem = self.generate_problem(skills, difficulty)
validation = self.validate_problem(problem)
if validation['is_valid'] and validation['score_alignment'] >= 4:
all_problems.append(problem)
else:
print(f"Problema rechazado: {validation['issues_found']}")
except Exception as e:
print(f"Error generando problema: {e}")
continue
return all_problems
Para garantizar cobertura completa del módulo:
def generate_complete_problem_bank(knowledge_space: KnowledgeSpaceBuilder):
"""Genera banco de problemas que cubre todo el espacio de conocimiento"""
generator = LLMProblemGenerator(api_key=os.getenv("ANTHROPIC_API_KEY"))
# Estrategia 1: Habilidades individuales (nivel básico)
single_skill_combinations = [[skill] for skill in knowledge_space.skills_db.values()]
# Estrategia 2: Pares de habilidades con relación de prerrequisito
prerequisite_pairs = [
[knowledge_space.skills_db[prereq], knowledge_space.skills_db[skill]]
for skill in knowledge_space.skill_graph.nodes()
for prereq in knowledge_space.skill_graph.predecessors(skill)
]
# Estrategia 3: Clusters de habilidades del mismo CE
ce_clusters = defaultdict(list)
for skill in knowledge_space.skills_db.values():
ce_clusters[skill.criterion_id].append(skill)
ce_combinations = list(ce_clusters.values())
# Estrategia 4: Problemas integradores (5-7 habilidades)
integrative_combinations = [
random.sample(list(knowledge_space.skills_db.values()), k=random.randint(5, 7))
for _ in range(20) # 20 problemas integradores
]
# Generar todos
all_combinations = (
single_skill_combinations +
prerequisite_pairs +
ce_combinations +
integrative_combinations
)
problems = generator.batch_generate(all_combinations, problems_per_combination=2)
print(f"✅ Generados {len(problems)} problemas cubriendo {len(knowledge_space.skills_db)} habilidades")
return problems
Estimación para MVP:
Coste estimado Claude 3.5 Sonnet: