Con este reto doy inicio a una serie de proyectos en AWS, que buscan afianzar conocimiento y explotar la creatividad, se utilizara la capa gratuita de AWS, lo que permitira a cualquier persona replicar este proyecto
He consolidado todos los aprendizajes, correcciones de errores (espacios en nombres, permisos, timeouts) y el código final robusto.
Proyecto: Extractor Inteligente de CVs con AWS Bedrock
(Principiante)
- Descripción General Este proyecto implementa una arquitectura Serverless en AWS para procesar automáticamente Hojas de Vida (CVs) en formato PDF. El sistema extrae texto, utiliza Inteligencia Artificial Generativa (Claude 3 Haiku) para estructurar la información (nombre, skills, contacto, etc.) en formato JSON y almacena los resultados en una base de datos NoSQL.
Flujo de Arquitectura
- Infraestructura
Amazon S3 (Almacenamiento)
Nombre del Bucket: resumenes-articulos-pdf-ia
Event Notification: Configurado para disparar la Lambda en eventos Put o CreateObject (sufijo .pdf).
Amazon DynamoDB (Base de Datos)
Nombre de la Tabla: resumenes-pdf-ia
Partition Key (Clave Principal): id_archivo (Tipo: String).
3.3. Amazon Bedrock (IA)
Modelo: anthropic.claude-3-haiku-20240307-v1:0
4. Seguridad (IAM Role)
Nombre del Rol: RoleLambdaSummarizer
Este rol sigue el principio de menor privilegio, permitiendo solo lo necesario para operar.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3Access",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::resumenes-articulos-pdf-ia",
"arn:aws:s3:::resumenes-articulos-pdf-ia/*"
]
},
{
"Sid": "BedrockInvoke",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel"
],
"Resource": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0"
},
{
"Sid": "DynamoDBWrite",
"Effect": "Allow",
"Action": [
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:*:table/resumenes-pdf-ia"
},
{
"Sid": "Logging",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
- Configuración de AWS Lambda
- Detalles Generales Runtime: Python 3.12 Timeout: 1 min 0 sec (Aumentado de 3s a 60s para esperar a la IA). Memory: 512 MB (Recomendado para procesar PDFs rápido).
Lambda Layer (Dependencia pypdf)
Para que Lambda pueda leer PDFs, se debe crear una Layer.
Instrucciones de creación (Windows):
Crear carpeta python.
Ejecutar: python -m pip install pypdf -t python/
Comprimir la carpeta python en layer_pypdf.zip.
Subir a AWS Lambda Layers y asociar a la función.
Código Fuente (lambda_function.py)
Este código incluye manejo de errores, decodificación de URLs (para archivos con espacios), parsing de JSON robusto y trazabilidad completa.
import json
import boto3
import io
import traceback
import pypdf
from urllib.parse import unquote_plus
from datetime import datetime
from botocore.exceptions import ClientError
# --- CONFIGURACIÓN ---
REGION = 'us-east-1'
TABLE_NAME = 'resumenes-pdf-ia'
MODEL_ID = 'anthropic.claude-3-haiku-20240307-v1:0'
# Clientes AWS
s3 = boto3.client('s3')
bedrock = boto3.client('bedrock-runtime', region_name=REGION)
dynamodb = boto3.resource('dynamodb', region_name=REGION)
table = dynamodb.Table(TABLE_NAME)
def extraer_texto_pdf(contenido_bytes):
"""Extrae texto plano de un archivo PDF en memoria."""
print("[SUB-PROCESO] Iniciando lectura de bytes PDF con pypdf...")
try:
pdf_file = io.BytesIO(contenido_bytes)
reader = pypdf.PdfReader(pdf_file)
num_paginas = len(reader.pages)
print(f"[SUB-PROCESO] PDF detectado con {num_paginas} páginas.")
texto_completo = ""
for i, page in enumerate(reader.pages):
texto_extraido = page.extract_text()
if texto_extraido:
texto_completo += texto_extraido + "\n"
return texto_completo
except Exception as e:
print(f"[ERROR] Fallo dentro de extraer_texto_pdf: {str(e)}")
raise e
def lambda_handler(event, context):
print("--- INICIO DE EJECUCIÓN (EXTRACCIÓN CV) ---")
try:
# 1. Obtener detalles del archivo
bucket = event['Records'][0]['s3']['bucket']['name']
raw_key = event['Records'][0]['s3']['object']['key']
# Decodificar nombre de archivo (arregla errores con espacios 'Hoja+de+vida.pdf')
key = unquote_plus(raw_key)
print(f"[PASO 1] Objetivo: {key} en Bucket: {bucket}")
# 2. Leer PDF de S3
print(f"[PASO 2] Descargando objeto de S3...")
response = s3.get_object(Bucket=bucket, Key=key)
file_content = response['Body'].read()
# 3. Convertir PDF a Texto
texto_candidato = extraer_texto_pdf(file_content)
print(f"[PASO 3] Extracción finalizada. Caracteres: {len(texto_candidato)}")
# 4. Preparar Prompt para Bedrock
prompt_sistema = """Eres un experto reclutador de TI. Tu tarea es extraer información de Hojas de Vida (CVs) en formato JSON estricto.
Extrae los siguientes campos:
- nombre_completo (String)
- telefono (String)
- correo (String)
- perfil_profesional (Resumen de 2 lineas)
- cargo_actual_o_ultimo (String)
- skills_tecnicos (Lista de Strings)
- profesion_titulo (String)
- certificaciones (Lista de Strings)
- universidad (String, si aplica)
- resumen_trayectoria (String, max 300 caracteres)
- tiene_foto (Boolean, basado en si el texto menciona "foto" o hay indicios visuales descritos)
IMPORTANTE: Responde ÚNICAMENTE con el objeto JSON."""
prompt_usuario = f"Analiza este CV:\n\n{texto_candidato[:50000]}"
body = json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 4000,
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": prompt_sistema},
{"type": "text", "text": prompt_usuario}
]
}
]
})
# 5. Invocar Bedrock
print(f"[PASO 5] Invocando Bedrock ({MODEL_ID})...")
response_bedrock = bedrock.invoke_model(
body=body,
modelId=MODEL_ID,
accept="application/json",
contentType="application/json"
)
response_body = json.loads(response_bedrock.get('body').read())
resultado_ai = response_body['content'][0]['text']
# 6. Parsing del JSON
print(f"[PASO 6] Parseando respuesta IA...")
start_idx = resultado_ai.find('{')
end_idx = resultado_ai.rfind('}') + 1
json_str = resultado_ai[start_idx:end_idx]
datos_candidato = json.loads(json_str)
# 7. Guardar en DynamoDB
datos_candidato['id_archivo'] = key
datos_candidato['fecha_procesamiento'] = str(datetime.now())
datos_candidato['bucket_origen'] = bucket
table.put_item(Item=datos_candidato)
print(f"[PASO 7] Datos guardados en DynamoDB.")
return {
'statusCode': 200,
'body': json.dumps('Candidato procesado exitosamente')
}
except Exception as e:
print("[ERROR FATAL]")
traceback.print_exc()
raise e
¡Reto Cumplido! Hemos domado a la IA en la Nube ☁️🤖
Si llegaste hasta aquí, ya tienes un "Reclutador IA" funcionando 100% en la nube. Hemos superado errores de permisos IAM, configurado Layers de Python y conectado S3 con DynamoDB.
¿Qué sigue? No nos vamos a quedar solo guardando datos. En el próximo tutorial, vamos a llevar esto al siguiente nivel con Embeddings y Búsqueda Semántica. ¡Prepárate para construir un buscador inteligente!
👇 Tu turno:
- ¿Qué otro uso se te ocurre para esta arquitectura? (¿Facturas? ¿Informes médicos?)
- Comenta "LOGRADO" si tu Lambda ya está corriendo.
- ¡Comparte este post con ese colega que sigue copiando datos a mano!



Top comments (0)