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