Agente desatendido clasificador

En términos sencillos, un Agente de IA no es solo un modelo que responde preguntas (como un chatbot común), sino un sistema capaz de actuar de forma autónoma para alcanzar un objetivo específico.

Mientras que un modelo de lenguaje (LLM) como Gemini funciona como un “cerebro” que procesa información, el Agente es el “cuerpo” que utiliza ese cerebro para razonar, usar herramientas y completar tareas complejas.

Se considera un “Agente”, debe cumplir con estos componentes:

  • Perfil / Rol: Es la identidad que le asignas (ej. “Eres un experto analista de documentos legales”). Esto condiciona cómo interpreta la información.
  • Memoria:
    • Corto plazo: El contexto de la conversación actual.
    • Largo plazo: En tu caso, la capacidad de extraer y recordar datos de los archivos PDF cargados.
  • Planificación: El agente desglosa una petición compleja (ej. “Haz un resumen de los gastos de este PDF”) en pasos lógicos: leer, filtrar, calcular y redactar.
  • Herramientas (Tools): Es la capacidad de interactuar con el mundo externo. Tu agente usa herramientas para leer el sistema de archivos de Linux y procesar binarios PDF. Estos son un ejemplo.

AgentePDF

Este AgentePDF es especialmente valioso porque resuelve el problema del “ruido” en los documentos. No se limita a extraer texto; entiende la estructura del documento. Usando Un criterio de clasificación logramos traer orden en nuestros archivos PDF. Clasificándolos como en una biblioteca.

Permite usar uno de dos de las inteligencias mas usadas al día de hoy ChatGPT o Gemini.

#!/usr/bin/env python3
import os
import json
import hashlib
import shutil
import re
import argparse
from datetime import datetime
from PyPDF2 import PdfReader




# ================= CONFIG =================
ENTRADA = os.path.expanduser("~/Entrada")
MANUAL = os.path.join(ENTRADA, "Manual")
BIBLIOTECA = os.path.expanduser("~/Biblioteca")

ITEMS_FILE = os.path.join(BIBLIOTECA, "items", "items.json")
LOG_FILE = os.path.join(BIBLIOTECA, "logs", "agente.log.json")

DEWEY_MAP = {
    "Filosofía": "Filosofia",
    "Religión": "Religion",
    "Ciencias sociales": "Ciencias_Sociales",
    "Lenguas": "Lenguas",
    "Ciencias": "Ciencias",
    "Tecnología": "Tecnologia",
    "Artes": "Artes",
    "Literatura": "Literatura",
    "Historia y geografía": "Historia_Geografia"
}

# ================= UTILIDADES =================
def log_event(data):
    os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
    with open(LOG_FILE, "a", encoding="utf-8") as f:
        f.write(json.dumps(data, ensure_ascii=False) + "\n")

def load_items():
    os.makedirs(os.path.dirname(ITEMS_FILE), exist_ok=True)
    if not os.path.exists(ITEMS_FILE):
        return {}
    with open(ITEMS_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_items(items):
    with open(ITEMS_FILE, "w", encoding="utf-8") as f:
        json.dump(items, f, indent=2, ensure_ascii=False)

def sha256(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            h.update(chunk)
    return h.hexdigest()

def extract_text_first_page(pdf_path):
    reader = PdfReader(pdf_path)
    text = reader.pages[0].extract_text()
    if not text:
        return None
    text = re.sub(r"[^A-Za-z0-9ÁÉÍÓÚáéíóúÑñ\s.,;:-]", "", text)
    return text.strip()

def build_prompt(text):
    return f"""
Clasifica el siguiente texto en UNA sola de las categorías Dewey:
Filosofía, Religión, Ciencias sociales, Lenguas, Ciencias,
Tecnología, Artes, Literatura, Historia y geografía.

Responde SOLO con el nombre exacto de la categoría.

Texto:
\"\"\"
{text[:3000]}
\"\"\"
"""

# ================= CLASIFICADORES =================
def classify_openai(text):
    from openai import OpenAI
    client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": build_prompt(text)}],
        temperature=0
    )
    return response.choices[0].message.content.strip()

def classify_gemini(text):
    #import google.generativeai as genai

    from google import genai
    client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))

    #model = genai.GenerativeModel("gemini-1.5-flash")
    #response = model.generate_content(build_prompt(text))

    response = client.models.generate_content(
        #gemini-2.5-flash
    model="gemini-2.5-flash", 
    contents=build_prompt(text=text))
    #print(build_prompt(text=text))
    
    return response.text.strip()

def get_classifier(provider):
    if provider == "openai":
        return classify_openai
    if provider == "gemini":
        return classify_gemini
    raise ValueError("Proveedor no soportado")

# ================= AGENTE =================
def run(provider):
    os.makedirs(MANUAL, exist_ok=True)

    items = load_items()
    pdfs = sorted([f for f in os.listdir(ENTRADA) if f.lower().endswith(".pdf")])

    if not pdfs:
        return

    pdf = pdfs[0]
    path = os.path.join(ENTRADA, pdf)
    checksum = sha256(path)

    if checksum in items:
        log_event({
            "timestamp": datetime.now().isoformat(),
            "action": "skip",
            "file": pdf,
            "reason": "already_processed"
        })
        return

    classifier = get_classifier(provider)

    try:
        text = extract_text_first_page(path)
        if not text:
            raise ValueError("no_text_extracted")

        category = classifier(text)
        folder = DEWEY_MAP.get(category)

        if not folder:
            raise ValueError(f"invalid_category: {category}")

        dest_dir = os.path.join(BIBLIOTECA, folder)
        os.makedirs(dest_dir, exist_ok=True)

        shutil.move(path, os.path.join(dest_dir, pdf))

        items[checksum] = {
            "file": pdf,
            "category": category,
            "provider": provider,
            "processed_at": datetime.now().isoformat()
        }
        save_items(items)

        log_event({
            "timestamp": datetime.now().isoformat(),
            "action": "classified",
            "file": pdf,
            "category": category,
            "provider": provider
        })

    except Exception as e:
        shutil.move(path, os.path.join(MANUAL, pdf))
        log_event({
            "timestamp": datetime.now().isoformat(),
            "action": "manual_review",
            "file": pdf,
            "error": str(e)
        })

# ================= MAIN =================
if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--provider",
        choices=["openai", "gemini"],
        default="gemini",
        help="Proveedor de IA"
    )
    args = parser.parse_args()
    run(args.provider)

Esta es una primera versión.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *