# app.py import gradio as gr import pandas as pd import PyPDF2 import requests import io import json import os # Importar el módulo os para acceder a variables de entorno # Variable global para almacenar el texto extraído de la base de conocimientos # Se vacía cada vez que se carga un nuevo archivo. knowledge_base_text = "" # Longitud máxima del contexto que se pasará al modelo de lenguaje. # Los modelos de lenguaje tienen límites en la cantidad de texto que pueden procesar. # Si la base de conocimientos es muy grande, se truncará. MAX_CONTEXT_LENGTH = 8000 # Caracteres def load_knowledge_base(file): """ Carga un archivo (CSV, XLSX, PDF) y extrae su contenido de texto para usarlo como base de conocimientos. Args: file: Un objeto de archivo de Gradio (gr.File), que contiene la ruta temporal del archivo subido. Returns: str: Un mensaje de estado indicando si la carga fue exitosa o si hubo un error. """ global knowledge_base_text knowledge_base_text = "" # Limpiar la base de conocimientos anterior al cargar una nueva if file is None: return "Por favor, sube un archivo para la base de conocimientos." file_path = file.name # La ruta temporal del archivo subido por Gradio file_extension = file_path.split('.')[-1].lower() try: if file_extension == 'csv': # Leer archivos CSV usando pandas df = pd.read_csv(file_path) knowledge_base_text = df.to_string(index=False) # Convertir DataFrame a cadena de texto elif file_extension == 'xlsx': # Leer archivos XLSX usando pandas (requiere openpyxl) df = pd.read_excel(file_path) knowledge_base_text = df.to_string(index=False) # Convertir DataFrame a cadena de texto elif file_extension == 'pdf': # Leer archivos PDF usando PyPDF2 with open(file_path, 'rb') as f: reader = PyPDF2.PdfReader(f) for page_num in range(len(reader.pages)): page = reader.pages[page_num] # Extraer texto de cada página y añadirlo a la base de conocimientos knowledge_base_text += page.extract_text() + "\n" else: return "Formato de archivo no soportado. Por favor, sube un archivo .csv, .xlsx o .pdf." # Truncar la base de conocimientos si excede la longitud máxima del contexto if len(knowledge_base_text) > MAX_CONTEXT_LENGTH: knowledge_base_text = knowledge_base_text[:MAX_CONTEXT_LENGTH] + "\n... [Contenido truncado debido a la longitud máxima del contexto]" return f"Base de conocimientos cargada exitosamente (truncada a {MAX_CONTEXT_LENGTH} caracteres). ¡Ahora puedes chatear!" else: return "Base de conocimientos cargada exitosamente. ¡Ahora puedes chatear!" except Exception as e: knowledge_base_text = "" # Limpiar la base de conocimientos en caso de error return f"Error al cargar la base de conocimientos: {e}. Asegúrate de que el archivo no esté corrupto o vacío." def get_llm_response(prompt_text, context_text, personality_setting): """ Genera una respuesta utilizando la API de Gemini, incorporando el contexto de la base de conocimientos y la personalidad seleccionada. Args: prompt_text (str): La pregunta del usuario. context_text (str): El texto de la base de conocimientos que se usará como contexto. personality_setting (str): La personalidad deseada para el bot (e.g., "amigable", "formal"). Returns: str: La respuesta generada por el modelo de lenguaje, o un mensaje de error. """ # Construir la instrucción del sistema para guiar el comportamiento del LLM system_instruction = ( f"Eres un asistente de IA con una personalidad {personality_setting}. " "Responde a las preguntas de manera útil y concisa, utilizando la información " "proporcionada en el contexto si es relevante. Si la respuesta no está en el contexto, " "usa tu conocimiento general. Responde siempre en español." ) # Combinar la instrucción del sistema, el contexto y la pregunta del usuario en un solo prompt full_prompt = f"{system_instruction}\n\nContexto:\n{context_text}\n\nPregunta: {prompt_text}" # *** CAMBIO IMPORTANTE AQUÍ: Leer la clave API de las variables de entorno *** api_key = os.getenv("GEMINI_API_KEY") if not api_key: return "Error: La clave API de Gemini no está configurada. Por favor, añádela como un secreto en Hugging Face Spaces (GEMINI_API_KEY)." api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={api_key}" # Preparar el historial de chat para la API de Gemini chat_history = [] chat_history.append({"role": "user", "parts": [{"text": full_prompt}]}) # Carga útil para la solicitud a la API de Gemini payload = { "contents": chat_history, "generationConfig": { "responseMimeType": "text/plain" # Solicitar una respuesta en texto plano } } try: # Realizar la solicitud POST a la API de Gemini response = requests.post(api_url, headers={'Content-Type': 'application/json'}, data=json.dumps(payload)) response.raise_for_status() # Lanzar una excepción para códigos de estado HTTP de error (4xx o 5xx) result = response.json() # Parsear la respuesta JSON # Extraer el texto de la respuesta del modelo if result.get("candidates") and len(result["candidates"]) > 0 and \ result["candidates"][0].get("content") and result["candidates"][0]["content"].get("parts") and \ len(result["candidates"][0]["content"]["parts"]) > 0: return result["candidates"][0]["content"]["parts"][0]["text"] else: print("Error: Estructura de respuesta inesperada de la API del LLM.", result) return "Lo siento, no pude generar una respuesta. Hubo un problema con la API del modelo." except requests.exceptions.RequestException as e: # Manejar errores de conexión o HTTP print(f"Error al llamar a la API de Gemini: {e}") return f"Lo siento, hubo un error de conexión al intentar obtener una respuesta: {e}" except json.JSONDecodeError as e: # Manejar errores al decodificar la respuesta JSON print(f"Error al decodificar la respuesta JSON de la API: {e}") return "Lo siento, hubo un problema al procesar la respuesta del modelo." except Exception as e: # Manejar cualquier otra excepción inesperada print(f"Ocurrió un error inesperado al obtener la respuesta del LLM: {e}") return "Lo siento, ocurrió un error inesperado al intentar obtener una respuesta." def chat(user_message, personality_setting): """ Función principal del chatbot que procesa el mensaje del usuario y genera una respuesta. Args: user_message (str): El mensaje o pregunta del usuario. personality_setting (str): La personalidad seleccionada para el bot. Returns: str: La respuesta generada por el bot. """ if not user_message: return "Por favor, escribe un mensaje para que el bot pueda responder." # Usar la base de conocimientos global como contexto para el LLM # Si no hay base de conocimientos cargada, se indica al LLM que no hay contexto. context_for_llm = knowledge_base_text if knowledge_base_text else "No hay una base de conocimientos cargada." # Obtener la respuesta del modelo de lenguaje bot_response = get_llm_response(user_message, context_for_llm, personality_setting) return bot_response # Configuración de la interfaz de Gradio with gr.Blocks(title="Chatbot de Base de Conocimientos") as demo: gr.Markdown( """ # 🤖 Chatbot de Base de Conocimientos Configurable Sube un archivo (CSV, XLSX, PDF) para que el bot lo use como base de conocimientos. Luego, selecciona una personalidad y haz tus preguntas. """ ) with gr.Row(): # Componente para subir archivos file_input = gr.File(label="Sube tu base de conocimientos (.csv, .xlsx, .pdf)", type="filepath") # Botón para cargar la base de conocimientos load_button = gr.Button("Cargar Base de Conocimientos") # Cuadro de texto para mostrar el estado de la carga status_output = gr.Textbox(label="Estado de la Carga", interactive=False) # Configurar la acción del botón de carga load_button.click( fn=load_knowledge_base, # Función a llamar inputs=file_input, # Entrada de la función outputs=status_output # Salida de la función ) gr.Markdown("---") # Separador visual with gr.Row(): # Desplegable para seleccionar la personalidad del bot personality_dropdown = gr.Dropdown( label="Personalidad del Bot", choices=["amigable", "formal", "creativo", "analítico"], # Opciones de personalidad value="amigable", # Valor predeterminado interactive=True ) # Cuadro de texto para que el usuario escriba su pregunta user_query_input = gr.Textbox(label="Tu Pregunta", placeholder="Escribe tu pregunta aquí...") # Botón para enviar la pregunta chat_button = gr.Button("Enviar Pregunta") # Cuadro de texto para mostrar la respuesta del bot bot_response_output = gr.Textbox(label="Respuesta del Bot", interactive=False) # Configurar la acción del botón de chat chat_button.click( fn=chat, # Función a llamar inputs=[user_query_input, personality_dropdown], # Entradas de la función outputs=bot_response_output # Salida de la función ) # Ejemplos para facilitar las pruebas rápidas del bot gr.Examples( examples=[ ["¿Qué es un chatbot?", "amigable"], ["Dame un resumen de la información cargada.", "analítico"], ["¿Cómo puedo usar este bot?", "formal"] ], inputs=[user_query_input, personality_dropdown], outputs=bot_response_output, fn=chat, # La función que se ejecuta al seleccionar un ejemplo cache_examples=False # No almacenar en caché los resultados de los ejemplos ) # Para ejecutar la aplicación Gradio (esto lo hará Hugging Face Spaces automáticamente) demo.launch()