The extraction schema defines how unstructured CV text is transformed into structured data using LangChain’s JsonOutputParser with Pydantic models. This system combines prompt engineering, LLM reasoning, and schema validation to extract student profiles from PDF resumes.
The extraction schema is optimized for student CVs where traditional work experience is limited but academic projects and technical skills are rich.
From source/notebook/Talent_Scout_3000x.ipynb:921:
from langchain_core.output_parsers import JsonOutputParserfrom pydantic import BaseModel, Field# Define the schema (see PerfilEstudiante reference)class PerfilEstudiante(BaseModel): nombre: str = Field(description="Nombre completo del estudiante") email: str = Field(description="Email universitario o personal") ubicacion: str = Field(description="Ciudad/País") universidad: str = Field(description="Nombre de la universidad o instituto") carrera: str = Field(description="Carrera que está estudiando (ej. Ing. Software)") ciclo_actual: str = Field(description="Ciclo o semestre actual (ej. 7mo Ciclo, Egresado)") stack_principal: list = Field(description="Lista de top 5 lenguajes/tecnologías que domina") proyectos_destacados: list = Field(description="Nombres de proyectos académicos, tesis o freelance mencionados") tipo_perfil: str = Field(description="Clasificar en: Backend, Frontend, Data, Fullstack o Gestión") potencial_contratacion: str = Field(description="Breve justificación de por qué contratarlo como practicante")# Create parser instanceparser = JsonOutputParser(pydantic_object=PerfilEstudiante)
The output should be formatted as a JSON instance that conforms to the JSON schema below.As an example, for the schema {"properties": {"foo": {"title": "Foo", "description": "a list of strings", "type": "array", "items": {"type": "string"}}}, "required": ["foo"]}the object {"foo": ["bar", "baz"]} is a well-formatted instance of the schema.Here is the output schema:{ "properties": { "nombre": {"type": "string", "description": "Nombre completo del estudiante"}, "email": {"type": "string", "description": "Email universitario o personal"}, ... }, "required": ["nombre", "email", "ubicacion", ...]}
From source/notebook/Talent_Scout_3000x.ipynb:924-950:
from langchain_core.prompts import ChatPromptTemplatetemplate_estudiantes = """Eres un Experto en Empleabilidad Joven y Reclutamiento IT.Analiza el CV de este estudiante y extrae los datos estructurados.UTILIZA EL SIGUIENTE FORMATO JSON:{format_instructions}REGLAS DE EXTRACCIÓN (Enfoque en Potencial):1. ACADÉMICO: - Busca el ciclo actual (ej. "VI Ciclo", "7no", "Egresado"). Si no dice, infiérelo por las fechas. - Universidad: Extrae el nombre principal (ej. "UTP", "UPC", "San Marcos").2. PROYECTOS (Clave para juniors): - Busca secciones como "Proyectos Académicos", "Freelance" o "Experiencia". - Extrae nombres de proyectos concretos (ej. "Sistema de Biblioteca", "App de Reciclaje"). - NO pongas nombres de empresas genéricas, busca QUÉ HIZO.3. TIPO DE PERFIL: - Analiza sus skills. - Si sabe Python + Pandas -> "Data". - Si sabe React + Node -> "Fullstack". - Si sabe Java + Spring -> "Backend".TEXTO DEL CV:{context}"""prompt_extract = ChatPromptTemplate.from_template(template_estudiantes)
“Eres un Experto en Empleabilidad Joven y Reclutamiento IT”Assigns the LLM an expert persona to improve reasoning quality. The model adopts the perspective of a recruiter focused on young talent.
Format Instructions Injection
{format_instructions} placeholder is replaced with the JSON schema at runtime, ensuring the LLM knows the exact output structure.
Domain-Specific Rules
REGLAS DE EXTRACCIÓN section provides heuristics for ambiguous cases:
How to infer academic cycle from dates
Focus on project names, not company names
Classification logic for profile types
Context Variable
{context} is filled with the CV text extracted from the PDF, providing the raw data for extraction.
FERNANDA PAREDESData Analyst Trainee[email protected] | +51 912 345 678 | Lima, PerúPERFIL DE ESTUDIANTEEstudiante de 9no ciclo con interés en Desarrollo de Software y Datos. Manejo de herramientas como Python y capacidad de aprendizaje rápido.PROYECTOS Y EXPERIENCIAData Analyst Trainee | Proyecto Académico (UTP)Jun 2025 - Feb 2026• Primer puesto en Hackathon universitaria desarrollando app de reciclaje.Tech: Python, PowerBI, Java, Spring BootFORMACIÓN ACADÉMICAUTP - Ingeniería de Software (En curso)
Extracted Output:
{ "nombre": "FERNANDA PAREDES", "email": "[email protected]", "ubicacion": "Lima, Perú", "universidad": "UTP", "carrera": "Ingeniería de Software", "ciclo_actual": "9no Ciclo", "stack_principal": ["Python", "PowerBI", "Java", "Spring Boot"], "proyectos_destacados": [ "Primer puesto en Hackathon universitaria desarrollando app de reciclaje" ], "tipo_perfil": "Data", "potencial_contratacion": "Fernanda es una candidata fuerte para Data Analyst Trainee. Su victoria en Hackathon demuestra capacidad de ejecución bajo presión, y su stack (Python, PowerBI) es coherente con análisis de datos."}
XIMENA RIOSJunior Python Developer[email protected] | Lima, PerúPERFIL DE ESTUDIANTEEstudiante de 9no ciclo con interés en Desarrollo de Software y Datos.PROYECTOS Y EXPERIENCIAJunior Python Developer | Startup UniversitariaMar 2025 - Feb 2026• Creación de una API RESTful para gestión financiera usando Python y FastAPI.Tech: Python, FastAPI, Java, React, Spring BootFORMACIÓN ACADÉMICASan Marcos - Ingeniería de Software (En curso)
Extracted Output:
{ "nombre": "XIMENA RIOS", "email": "[email protected]", "ubicacion": "Lima, Perú", "universidad": "San Marcos", "carrera": "Ingeniería de Software", "ciclo_actual": "9no ciclo", "stack_principal": ["Python", "FastAPI", "Java", "React", "Spring Boot"], "proyectos_destacados": [ "Creación de una API RESTful para gestión financiera usando Python y FastAPI" ], "tipo_perfil": "Fullstack", "potencial_contratacion": "Estudiante avanzado (9no ciclo) con experiencia práctica en la creación de APIs RESTful (FastAPI) y automatización de tareas (Python/Pandas). Su manejo de tecnologías tanto de Backend (Java, Spring Boot) como de Frontend (React) demuestra versatilidad técnica."}
If a field is truly missing from the CV, provide a default:
# In the prompt, add a fallback ruletemplate_estudiantes = """...4. VALORES POR DEFECTO: - Si no encuentras el email, usa "[email protected]" - Si no hay proyectos mencionados, usa ["Sin proyectos destacados mencionados"]..."""
llm = ChatGoogleGenerativeAI( model="gemini-1.5-flash", temperature=0 # Deterministic for consistent extraction)
Use temperature=0 for data extraction tasks to ensure consistent, deterministic outputs. Higher temperatures introduce randomness that can break schema validation.
Add Pydantic validators for additional data quality checks:
from pydantic import BaseModel, Field, validatorclass PerfilEstudiante(BaseModel): # ... other fields ... stack_principal: list = Field(description="...") @validator('stack_principal') def validate_stack_length(cls, v): if len(v) > 5: return v[:5] # Truncate to top 5 if len(v) == 0: raise ValueError("stack_principal cannot be empty") return v @validator('ciclo_actual') def validate_ciclo_format(cls, v): if "ciclo" not in v.lower() and v != "Egresado": raise ValueError(f"Invalid ciclo format: {v}") return v