DEV Community

Cover image for AWSChallenge - Día 1
Andres
Andres

Posted on

AWSChallenge - Día 1

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)

  1. 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

  1. 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": "*"
        }
    ]
}

Enter fullscreen mode Exit fullscreen mode
  1. 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

Enter fullscreen mode Exit fullscreen mode

¡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)