import os import json import logging import io from typing import Dict, Any from fastapi import FastAPI, UploadFile, File, HTTPException, Depends from fastapi.responses import FileResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field, field_validator from groq import Groq import pdfplumber # ==================== CONFIGURACIÓN ==================== logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) app = FastAPI(title="PragmaLens API", version="1.1.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) # ==================== DEPENDENCIAS ==================== def get_groq_client(): api_key = os.getenv("GROQ_API_KEY") if not api_key: raise HTTPException(status_code=500, detail="GROQ_API_KEY no configurada") return Groq(api_key=api_key) # ==================== MODELOS ==================== class TextInput(BaseModel): text: str = Field(..., min_length=1) @field_validator('text') @classmethod def text_not_empty(cls, v: str) -> str: if not v.strip(): raise ValueError('El texto no puede estar vacío') return v.strip() # ==================== LÓGICA DE ANÁLISIS ==================== def run_audit(text: str, client: Groq) -> Dict[str, str]: # El prompt instruye a la IA para detectar el idioma automáticamente y responder en el mismo system_prompt = ( "You are an expert polyglot discourse analyst. " "1. Automatically detect the language of the input text. " "2. Analyze for: Grice's Maxims, Pragmatic markers, Ambiguity, and Logical fallacies. " "3. Respond in the same language as the input text. " "Return ONLY a raw JSON object with these exact keys: " "'grice_maxims', 'pragmatics', 'ambiguity', 'fallacies'. " "Each value must be a single continuous string." ) try: response = client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": text[:4000]} ], response_format={"type": "json_object"}, temperature=0.1 ) return json.loads(response.choices[0].message.content) except Exception as e: logger.error(f"Error en auditoría: {e}") return { "grice_maxims": "Error processing", "pragmatics": "Error processing", "ambiguity": "Error processing", "fallacies": "Error processing" } # ==================== ENDPOINTS ==================== @app.get("/") async def root(): return FileResponse("index.html") @app.post("/analyze") async def analyze(payload: TextInput, client: Groq = Depends(get_groq_client)): return run_audit(payload.text, client) @app.post("/analyze_pdf") async def analyze_pdf(file: UploadFile = File(...), client: Groq = Depends(get_groq_client)): if not file.filename.lower().endswith('.pdf'): raise HTTPException(status_code=400, detail="Solo se aceptan archivos PDF") content = await file.read() with pdfplumber.open(io.BytesIO(content)) as pdf: # Extraemos hasta 10 páginas para optimizar tokens text = "\n".join([p.extract_text() or "" for p in pdf.pages[:10]]) if not text.strip(): raise HTTPException(status_code=400, detail="No se pudo extraer texto del PDF") return run_audit(text, client)