JMAA00 commited on
Commit
adc63f0
·
1 Parent(s): 87fd43c
Files changed (1) hide show
  1. app.py +126 -137
app.py CHANGED
@@ -5,22 +5,28 @@ from huggingface_hub import InferenceClient
5
 
6
  """
7
  For more information on `huggingface_hub` Inference API support,
8
- please check the docs:
9
  https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
10
  """
11
 
12
- # ============================
13
- # CONFIGURACIÓN SERPER
14
- # ============================
15
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
16
 
17
  def do_websearch(query: str) -> str:
18
- """ Llama a serper.dev y retorna los resultados. """
 
 
 
19
  if not SERPER_API_KEY:
20
  return "(SERPER_API_KEY no está configurado)"
21
 
22
  url = "https://google.serper.dev/search"
23
- headers = {"X-API-KEY": SERPER_API_KEY, "Content-Type": "application/json"}
 
 
 
24
  payload = {"q": query}
25
 
26
  try:
@@ -29,160 +35,143 @@ def do_websearch(query: str) -> str:
29
  except Exception as e:
30
  return f"(Error al llamar a serper.dev: {e})"
31
 
 
32
  if "organic" not in data:
33
  return "No se encontraron resultados en serper.dev."
 
34
  results = data["organic"]
35
  if not results:
36
  return "No hay resultados relevantes."
37
 
38
- lines = []
39
  for i, item in enumerate(results, start=1):
40
  title = item.get("title", "Sin título")
41
  link = item.get("link", "Sin enlace")
42
- lines.append(f"{i}. {title}\n {link}")
43
- return "\n".join(lines)
44
 
 
45
 
46
- # ============================
47
- # CONFIGURACIÓN DEL MODELO
48
- # ============================
 
 
49
  hf_api_token = os.getenv("HF_API_TOKEN")
 
 
50
  client = InferenceClient(
51
  model="meta-llama/Llama-3.1-8B-Instruct",
52
- token=hf_api_token
53
  )
54
 
55
- # ============================
56
- # FUNCIÓN PRINCIPAL (sin streaming)
57
- # ============================
58
- def chat_fn(user_msg, chat_history, system_msg, max_tokens, temperature, top_p, use_search):
 
 
 
 
 
59
  """
60
- Llamado al pulsar "Enviar".
61
- - user_msg: mensaje de usuario
62
- - use_search: bool, si se hace la búsqueda
63
- - chat_history: lista de dicts con formato messages
64
- e.g. [{"role": "user", "content": ...}, {"role": "assistant", "content": ...}, ...]
65
- - system_msg: se agrega como primer mensaje de rol system (si no está ya)
66
- Retorna la nueva historia (formato 'messages'), con el nuevo assistant message.
67
  """
68
 
69
- # 1) Revisamos si en el chat_history ya está el system. Si no, lo añadimos al principio.
70
- if not chat_history or chat_history[0]["role"] != "system":
71
- chat_history.insert(0, {"role": "system", "content": system_msg})
72
-
73
- # 2) Mezclamos el user_msg + websearch (si use_search=True)
74
- merged_prompt = user_msg
75
  if use_search:
76
- web_info = do_websearch(user_msg)
77
- merged_prompt = f"{user_msg}\nInformación de la web:\n{web_info}"
78
-
79
- # 3) Añadimos nuevo rol user
80
- chat_history.append({"role": "user", "content": merged_prompt})
81
-
82
- # 4) Llamada a la API (sin streaming)
83
- response = client.chat_completion(
84
- messages=chat_history,
 
 
 
 
 
 
 
 
 
 
85
  max_tokens=max_tokens,
 
86
  temperature=temperature,
87
  top_p=top_p,
88
- stream=False
89
- )
90
- # Obtenemos el texto
91
- assistant_reply = response.choices[0].message["content"]
92
-
93
- # 5) Añadimos la respuesta del assistant
94
- chat_history.append({"role": "assistant", "content": assistant_reply})
95
-
96
- return chat_history
97
-
98
-
99
- # ============================
100
- # CSS y DISEÑO
101
- # ============================
102
- css_code = """
103
- #input_container {
104
- position: relative;
105
- margin-top: 1rem;
106
- display: flex;
107
- align-items: flex-start;
108
- gap: 0.5rem;
109
- }
110
- #search_toggle {
111
- position: absolute;
112
- left: 0;
113
- bottom: -1.8rem;
114
- background: #fff;
115
- border: 1px solid #ccc;
116
- padding: 0.3rem 0.5rem;
117
- border-radius: 0.25rem;
118
- font-size: 0.9rem;
119
- color: #333;
120
- cursor: pointer;
121
- display: flex;
122
- align-items: center;
123
- }
124
-
125
- #search_toggle input[type="checkbox"]:checked + label {
126
- background-color: #0272f5; /* color principal de gradio */
127
- color: white;
128
- border: none;
129
- }
130
- """
131
-
132
- with gr.Blocks(css=css_code) as demo:
133
- gr.Markdown("## Chat con WebSearch (Gradio 5.0.1) - Sin streaming, sin gr.Box")
134
-
135
- # Chatbot en formato 'messages' para evitar el warning sobre 'tuples'.
136
- chatbot = gr.Chatbot(
137
- label="Conversación",
138
- value=[],
139
- type="messages" # Evitamos la advertencia de tuplas deprecadas
140
- )
141
-
142
- # Historial "messages" en un gr.State
143
- chat_history = gr.State([])
144
-
145
- # Config
146
- system_box = gr.Textbox(
147
- label="Mensaje del sistema",
148
- value="Eres Juan, un asistente virtual en español. Ayuda con amabilidad."
149
- )
150
- max_tokens_slider = gr.Slider(1, 2048, 512, step=1, label="Máxima cantidad de tokens")
151
- temp_slider = gr.Slider(0.1, 4.0, 0.7, step=0.1, label="Temperatura")
152
- top_p_slider = gr.Slider(0.1, 1.0, 0.95, step=0.05, label="Top-p")
153
-
154
- with gr.Column():
155
- with gr.Row(elem_id="input_container"):
156
- user_input = gr.Textbox(
157
- show_label=False,
158
- placeholder="Escribe tu mensaje...",
159
- lines=3
160
- )
161
- # Checkbox en la parte inferior izq
162
- with gr.Row(elem_id="search_toggle"):
163
- search_checkbox = gr.Checkbox(value=False)
164
- gr.Label("🌐 Búsqueda")
165
-
166
- send_btn = gr.Button("Enviar")
167
-
168
- # Lógica: 1) chat_fn => 2) actualizamos Chatbot
169
- def update_chat_display(history):
170
- # El Chatbot 'messages' recibe [{"role": "...", "content": "..."}]
171
- return history
172
-
173
- send_btn.click(
174
- fn=chat_fn,
175
- inputs=[user_input, chat_history, system_box, max_tokens_slider, temp_slider, top_p_slider, search_checkbox],
176
- outputs=chat_history
177
- ).then(
178
- fn=update_chat_display,
179
- inputs=chat_history,
180
- outputs=chatbot
181
- ).then(
182
- fn=lambda: "",
183
- inputs=None,
184
- outputs=user_input
185
- )
186
 
187
  if __name__ == "__main__":
188
  demo.launch()
 
5
 
6
  """
7
  For more information on `huggingface_hub` Inference API support,
8
+ please check the docs:
9
  https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
10
  """
11
 
12
+ # ----------------------------------------------------------------
13
+ # CONFIGURACIÓN DE SERPER (búsqueda web)
14
+ # ----------------------------------------------------------------
15
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
16
 
17
  def do_websearch(query: str) -> str:
18
+ """
19
+ Llama a serper.dev para hacer la búsqueda en Google y devolver
20
+ un texto resumido de los resultados.
21
+ """
22
  if not SERPER_API_KEY:
23
  return "(SERPER_API_KEY no está configurado)"
24
 
25
  url = "https://google.serper.dev/search"
26
+ headers = {
27
+ "X-API-KEY": SERPER_API_KEY,
28
+ "Content-Type": "application/json",
29
+ }
30
  payload = {"q": query}
31
 
32
  try:
 
35
  except Exception as e:
36
  return f"(Error al llamar a serper.dev: {e})"
37
 
38
+ # Se espera un campo 'organic' con resultados
39
  if "organic" not in data:
40
  return "No se encontraron resultados en serper.dev."
41
+
42
  results = data["organic"]
43
  if not results:
44
  return "No hay resultados relevantes."
45
 
46
+ text = []
47
  for i, item in enumerate(results, start=1):
48
  title = item.get("title", "Sin título")
49
  link = item.get("link", "Sin enlace")
50
+ text.append(f"{i}. {title}\n {link}")
 
51
 
52
+ return "\n".join(text)
53
 
54
+
55
+ # ----------------------------------------------------------------
56
+ # CONFIGURACIÓN DEL MODELO (AHORA CON TOKEN)
57
+ # ----------------------------------------------------------------
58
+ # Cargamos el token desde la variable de entorno 'HF_API_TOKEN'
59
  hf_api_token = os.getenv("HF_API_TOKEN")
60
+
61
+ # Usamos el modelo meta-llama/Llama-3.1-8B-Instruct con acceso a tu cuenta
62
  client = InferenceClient(
63
  model="meta-llama/Llama-3.1-8B-Instruct",
64
+ token=hf_api_token # <-- la clave para evitar el 401 Unauthorized
65
  )
66
 
67
+ def respond(
68
+ message,
69
+ history: list[tuple[str, str]],
70
+ system_message,
71
+ max_tokens,
72
+ temperature,
73
+ top_p,
74
+ use_search # <-- Nuevo parámetro: si está "activado" el botón
75
+ ):
76
  """
77
+ - system_message: Texto del rol "system"
78
+ - history: lista de (user_msg, assistant_msg)
79
+ - message: Mensaje actual del usuario
80
+ - use_search: booleano que indica si se habilita la búsqueda en serper
 
 
 
81
  """
82
 
83
+ # (1) Solo se llama a la websearch si use_search está activo
84
+ # (2) Se mezclan los resultados en primer lugar + el mensaje del usuario en segundo lugar
 
 
 
 
85
  if use_search:
86
+ web_info = do_websearch(message)
87
+ # "En primer lugar el resultado del scraper y luego el input del usuario"
88
+ message = f"Información de la web:\n{web_info}\n\nPregunta del usuario:\n{message}"
89
+
90
+ # Construimos la lista de mensajes
91
+ messages = [{"role": "system", "content": system_message}]
92
+ for user_txt, assistant_txt in history:
93
+ if user_txt:
94
+ messages.append({"role": "user", "content": user_txt})
95
+ if assistant_txt:
96
+ messages.append({"role": "assistant", "content": assistant_txt})
97
+
98
+ # Añadimos el nuevo turno del usuario
99
+ messages.append({"role": "user", "content": message})
100
+
101
+ # Llamamos a la API con streaming
102
+ response_text = ""
103
+ for chunk in client.chat_completion(
104
+ messages=messages,
105
  max_tokens=max_tokens,
106
+ stream=True,
107
  temperature=temperature,
108
  top_p=top_p,
109
+ ):
110
+ token = chunk.choices[0].delta.get("content", "")
111
+ response_text += token
112
+ yield response_text
113
+
114
+
115
+ # ----------------------------------------------------------------
116
+ # CONFIGURACIÓN DE LA INTERFAZ
117
+ # ----------------------------------------------------------------
118
+ # Ejemplo de Tailwind classes (puedes personalizarlas):
119
+ tailwind_toggle_classes = [
120
+ "inline-flex",
121
+ "items-center",
122
+ "bg-blue-500",
123
+ "hover:bg-blue-700",
124
+ "text-white",
125
+ "font-bold",
126
+ "py-1",
127
+ "px-2",
128
+ "rounded",
129
+ "cursor-pointer"
130
+ ]
131
+
132
+ # ChatInterface, con un input Checkbox para "🌐 Búsqueda"
133
+ demo = gr.ChatInterface(
134
+ fn=respond,
135
+ additional_inputs=[
136
+ gr.Textbox(
137
+ value=(
138
+ "Eres Juan, un asistente virtual en español. "
139
+ "Debes responder con mucha paciencia y empatía a usuarios que "
140
+ "pueden tener dificultades cognitivas o escribir frases confusas. "
141
+ "Provee explicaciones simples, procura entender la intención del usuario "
142
+ "aunque la frase esté mal escrita, y mantén siempre un tono amable."
143
+ ),
144
+ label="Mensaje del sistema",
145
+ ),
146
+ gr.Slider(
147
+ minimum=1,
148
+ maximum=2048,
149
+ value=512,
150
+ step=1,
151
+ label="Máxima cantidad de tokens"
152
+ ),
153
+ gr.Slider(
154
+ minimum=0.1,
155
+ maximum=4.0,
156
+ value=0.7,
157
+ step=0.1,
158
+ label="Temperatura"
159
+ ),
160
+ gr.Slider(
161
+ minimum=0.1,
162
+ maximum=1.0,
163
+ value=0.95,
164
+ step=0.05,
165
+ label="Top-p (muestreo por núcleo)",
166
+ ),
167
+ # Un checkbox para habilitar "búsqueda web" (use_search)
168
+ gr.Checkbox(
169
+ value=False,
170
+ label="🌐 Búsqueda",
171
+ elem_classes=tailwind_toggle_classes
172
+ ),
173
+ ],
174
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  if __name__ == "__main__":
177
  demo.launch()