File size: 10,527 Bytes
5ece554 d5d43c4 5ece554 d5d43c4 5ece554 e698e88 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# 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()
|