byArMan456 commited on
Commit
0a8b870
·
verified ·
1 Parent(s): 903329c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -56
app.py CHANGED
@@ -18,6 +18,11 @@ from langchain_community.tools.tavily_research import TavilySearchResults
18
  # Para manejar variables de entorno (opcional si solo usas secretos de HF)
19
  from dotenv import load_dotenv
20
 
 
 
 
 
 
21
  # Cargar variables de entorno si estás desarrollando localmente
22
  # load_dotenv() # Descomenta si desarrollas localmente y tienes .env
23
 
@@ -31,7 +36,6 @@ class BasicAgent:
31
  print("BasicAgent initialized.")
32
 
33
  # --- 1. Configuración de las Claves API ---
34
- # Se leerán automáticamente de los secretos del Space de HF o de .env si load_dotenv() se usa.
35
  google_api_key = os.getenv("GOOGLE_API_KEY")
36
  tavily_api_key = os.getenv("TAVILY_API_KEY")
37
 
@@ -41,49 +45,41 @@ class BasicAgent:
41
  raise ValueError("TAVILY_API_KEY environment variable not set. Please add it to your Hugging Face Space secrets.")
42
 
43
  # --- 2. Inicialización del LLM (Gemini Pro) ---
44
- # Puedes probar con "gemini-pro" o "gemini-1.5-pro" si tienes acceso y tu Space lo soporta.
45
  self.llm = ChatGoogleGenerativeAI(
46
  model="gemini-pro",
47
  google_api_key=google_api_key,
48
- temperature=0.1, # Más bajo para respuestas más consistentes y precisas
49
- max_tokens=2048 # Aumenta si necesitas respuestas más largas
50
  )
51
  print("LLM (Gemini Pro) initialized.")
52
 
53
  # --- 3. Definición de Herramientas ---
54
- # a) Tavily Search (MUY IMPORTANTE para GAIA)
55
- self.tavily_search_tool = TavilySearchResults(api_key=tavily_api_key, max_results=5) # max_results limita los resultados
56
 
57
- # b) Herramienta para descargar y leer archivos (adaptada de nuestra conversación anterior)
58
- # Asegúrate de que DEFAULT_API_URL esté accesible en el scope de la clase
59
  self.api_url = DEFAULT_API_URL
60
 
61
  def download_and_read_task_file(task_id: str, filename: str) -> str:
62
  """
63
  Downloads a file associated with a task_id from the API and returns its content.
64
  Supports text, CSV, JSON, and PDF files.
65
- Args:
66
- task_id (str): The ID of the task associated with the file.
67
- filename (str): The name of the file to download (e.g., "document.pdf", "data.csv").
68
- Returns:
69
- str: The content of the file as a string.
70
  """
71
  file_url = f"{self.api_url}/files/{task_id}"
72
  print(f"DEBUG: Attempting to download file from: {file_url} (filename: {filename})")
73
  try:
74
  response = requests.get(file_url, timeout=30, stream=True)
75
- response.raise_for_status() # Raise an exception for bad status codes
76
 
77
- temp_filepath = f"/tmp/{filename}" # Use /tmp for ephemeral storage in Space
78
  with open(temp_filepath, 'wb') as f:
79
  for chunk in response.iter_content(chunk_size=8192):
80
  f.write(chunk)
81
 
82
  content = ""
83
- # Process the file based on its extension
84
  if filename.lower().endswith(".pdf"):
85
  try:
86
- import pymupdf # type: ignore
87
  doc = pymupdf.open(temp_filepath)
88
  for page in doc:
89
  content += page.get_text()
@@ -96,15 +92,13 @@ class BasicAgent:
96
  with open(temp_filepath, 'r', encoding='utf-8') as f:
97
  content = f.read()
98
  elif filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
99
- # Para imágenes, necesitarías OCR. Esto es más complejo.
100
- # Por ahora, solo indicamos que se descargó.
101
  content = f"Image file '{filename}' downloaded. OCR not implemented."
102
  else:
103
  content = f"Unsupported file type for {filename}. Cannot extract content."
104
 
105
- os.remove(temp_filepath) # Clean up the temporary file
106
 
107
- return f"Content of {filename}:\n{content[:1000]}..." # Return first 1000 chars for brevity
108
  except requests.exceptions.RequestException as e:
109
  return f"Error downloading file '{filename}' from {file_url}: {e}"
110
  except Exception as e:
@@ -121,19 +115,10 @@ class BasicAgent:
121
  func=download_and_read_task_file,
122
  description="Descarga y lee el contenido de un archivo asociado a una tarea específica. Necesita 'task_id' (string) y 'filename' (string, e.g., 'document.pdf', 'data.csv'). Útil cuando la pregunta menciona archivos.",
123
  ),
124
- # Puedes añadir más herramientas aquí:
125
- # - Python REPL si necesitas ejecutar código Python
126
- # - Wikipedia si quieres una fuente de conocimiento enciclopédico
127
- # - Herramienta de cálculo, etc.
128
  ]
129
  print(f"Agent initialized with {len(self.tools)} tools.")
130
 
131
-
132
  # --- 4. Construir el Agente ReAct ---
133
- # El prompt estándar de ReAct para modelos de chat
134
- # prompt = hub.pull("hwchase17/react-chat") # Puedes probar este también
135
-
136
- # Customización del prompt para "EXACT MATCH"
137
  custom_prompt_template = """Responde la siguiente pregunta de forma concisa y directa. Tu objetivo es proporcionar un "EXACT MATCH" con la respuesta correcta.
138
 
139
  INSTRUCCIONES CLAVE:
@@ -150,51 +135,34 @@ class BasicAgent:
150
  {agent_scratchpad}"""
151
 
152
  self.prompt = PromptTemplate.from_template(custom_prompt_template)
153
-
154
- # Para create_react_agent, necesitamos decirle qué herramientas están disponibles y sus nombres.
155
- # Esto se hace automáticamente si usas hub.pull("hwchase17/react-chat-json") o similar
156
- # Pero con un prompt customizado, lo hacemos explícito.
157
  self.prompt = self.prompt.partial(
158
  tools="\n".join([f"{tool.name}: {tool.description}" for tool in self.tools]),
159
  tool_names=", ".join([tool.name for tool in self.tools]),
160
  )
161
 
162
-
163
  self.agent = create_react_agent(self.llm, self.tools, self.prompt)
164
- # verbose=True es ESENCIAL para depurar y ver el razonamiento del agente.
165
- # handle_parsing_errors=True para que el agente intente recuperarse de errores de formato.
166
  self.agent_executor = AgentExecutor(
167
  agent=self.agent,
168
  tools=self.tools,
169
  verbose=True,
170
  handle_parsing_errors=True,
171
- max_iterations=15, # Limita el número de pasos para evitar bucles infinitos
172
- agent_kwargs={"handle_parsing_errors": True} # También en el agente
173
  )
174
  print("AgentExecutor initialized.")
175
 
176
  def __call__(self, question: str) -> str:
177
  print(f"\n--- Processing new question (first 100 chars): {question[:100]}...")
178
  try:
179
- # Si la pregunta incluye "Task ID: [ID]", extrae el task_id.
180
- # GAIA a menudo hace esto para indicar el archivo.
181
  task_id_match = re.search(r"Task ID:\s*([\w-]+)", question)
182
  current_task_id = task_id_match.group(1) if task_id_match else None
183
  print(f"Detected Task ID for question: {current_task_id}")
184
 
185
-
186
- # Invocar al agente. Aquí es donde el agente usa el LLM y las herramientas.
187
- # Pasamos el task_id para que la herramienta de descarga de archivos pueda usarlo si es necesario.
188
- # Algunos agentes pueden requerir un formato de input diferente, ReAct usa "input".
189
  response = self.agent_executor.invoke({"input": question, "task_id": current_task_id})
190
- # La respuesta final del agente React suele estar en 'output'
191
  final_answer = response.get("output", "No answer generated by agent.")
192
 
193
- # Post-procesamiento para asegurar EXACT MATCH y eliminar texto adicional
194
- # Esto es CRÍTICO para el scoring.
195
  final_answer = final_answer.strip()
196
- # Elimina cualquier texto como "La respuesta es:", "The final answer is:", etc.
197
- # Haz esto solo si detectas que el LLM aún añade verbosidad.
198
  # if final_answer.lower().startswith(("the answer is", "la respuesta es", "final answer:")):
199
  # final_answer = re.sub(r"^[Tt]he [Aa]nswer is: |^[Ll]a [Rr]espuesta es: |^[Ff]inal [Aa]nswer: ", "", final_answer, flags=re.IGNORECASE).strip()
200
 
@@ -202,12 +170,174 @@ class BasicAgent:
202
  return final_answer
203
  except Exception as e:
204
  print(f"Error during agent execution: {e}")
205
- # En caso de error, puedes devolver un mensaje de error o una cadena vacía.
206
- # Para el scoring, una cadena vacía o un error puede contar como incorrecto.
207
  return f"ERROR: {e}"
208
 
209
- # El resto del código de `run_and_submit_all` y Gradio permanece igual.
210
- # ... (código existente) ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
- # Asegúrate de importar 're' al principio del archivo si lo usas para task_id_match
213
- import re
 
 
18
  # Para manejar variables de entorno (opcional si solo usas secretos de HF)
19
  from dotenv import load_dotenv
20
 
21
+ # Para extraer el Task ID de la pregunta
22
+ import re
23
+ # Para leer PDFs
24
+ import pymupdf # Asegúrate de que este import esté aquí si lo usas en download_and_read_task_file
25
+
26
  # Cargar variables de entorno si estás desarrollando localmente
27
  # load_dotenv() # Descomenta si desarrollas localmente y tienes .env
28
 
 
36
  print("BasicAgent initialized.")
37
 
38
  # --- 1. Configuración de las Claves API ---
 
39
  google_api_key = os.getenv("GOOGLE_API_KEY")
40
  tavily_api_key = os.getenv("TAVILY_API_KEY")
41
 
 
45
  raise ValueError("TAVILY_API_KEY environment variable not set. Please add it to your Hugging Face Space secrets.")
46
 
47
  # --- 2. Inicialización del LLM (Gemini Pro) ---
 
48
  self.llm = ChatGoogleGenerativeAI(
49
  model="gemini-pro",
50
  google_api_key=google_api_key,
51
+ temperature=0.1,
52
+ max_tokens=2048
53
  )
54
  print("LLM (Gemini Pro) initialized.")
55
 
56
  # --- 3. Definición de Herramientas ---
57
+ # a) Tavily Search
58
+ self.tavily_search_tool = TavilySearchResults(api_key=tavily_api_key, max_results=5)
59
 
60
+ # b) Herramienta para descargar y leer archivos
 
61
  self.api_url = DEFAULT_API_URL
62
 
63
  def download_and_read_task_file(task_id: str, filename: str) -> str:
64
  """
65
  Downloads a file associated with a task_id from the API and returns its content.
66
  Supports text, CSV, JSON, and PDF files.
 
 
 
 
 
67
  """
68
  file_url = f"{self.api_url}/files/{task_id}"
69
  print(f"DEBUG: Attempting to download file from: {file_url} (filename: {filename})")
70
  try:
71
  response = requests.get(file_url, timeout=30, stream=True)
72
+ response.raise_for_status()
73
 
74
+ temp_filepath = f"/tmp/{filename}"
75
  with open(temp_filepath, 'wb') as f:
76
  for chunk in response.iter_content(chunk_size=8192):
77
  f.write(chunk)
78
 
79
  content = ""
 
80
  if filename.lower().endswith(".pdf"):
81
  try:
82
+ # Asegúrate de que pymupdf esté instalado en requirements.txt
83
  doc = pymupdf.open(temp_filepath)
84
  for page in doc:
85
  content += page.get_text()
 
92
  with open(temp_filepath, 'r', encoding='utf-8') as f:
93
  content = f.read()
94
  elif filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
 
 
95
  content = f"Image file '{filename}' downloaded. OCR not implemented."
96
  else:
97
  content = f"Unsupported file type for {filename}. Cannot extract content."
98
 
99
+ os.remove(temp_filepath)
100
 
101
+ return f"Content of {filename}:\n{content[:1000]}..."
102
  except requests.exceptions.RequestException as e:
103
  return f"Error downloading file '{filename}' from {file_url}: {e}"
104
  except Exception as e:
 
115
  func=download_and_read_task_file,
116
  description="Descarga y lee el contenido de un archivo asociado a una tarea específica. Necesita 'task_id' (string) y 'filename' (string, e.g., 'document.pdf', 'data.csv'). Útil cuando la pregunta menciona archivos.",
117
  ),
 
 
 
 
118
  ]
119
  print(f"Agent initialized with {len(self.tools)} tools.")
120
 
 
121
  # --- 4. Construir el Agente ReAct ---
 
 
 
 
122
  custom_prompt_template = """Responde la siguiente pregunta de forma concisa y directa. Tu objetivo es proporcionar un "EXACT MATCH" con la respuesta correcta.
123
 
124
  INSTRUCCIONES CLAVE:
 
135
  {agent_scratchpad}"""
136
 
137
  self.prompt = PromptTemplate.from_template(custom_prompt_template)
 
 
 
 
138
  self.prompt = self.prompt.partial(
139
  tools="\n".join([f"{tool.name}: {tool.description}" for tool in self.tools]),
140
  tool_names=", ".join([tool.name for tool in self.tools]),
141
  )
142
 
 
143
  self.agent = create_react_agent(self.llm, self.tools, self.prompt)
 
 
144
  self.agent_executor = AgentExecutor(
145
  agent=self.agent,
146
  tools=self.tools,
147
  verbose=True,
148
  handle_parsing_errors=True,
149
+ max_iterations=15,
150
+ agent_kwargs={"handle_parsing_errors": True}
151
  )
152
  print("AgentExecutor initialized.")
153
 
154
  def __call__(self, question: str) -> str:
155
  print(f"\n--- Processing new question (first 100 chars): {question[:100]}...")
156
  try:
 
 
157
  task_id_match = re.search(r"Task ID:\s*([\w-]+)", question)
158
  current_task_id = task_id_match.group(1) if task_id_match else None
159
  print(f"Detected Task ID for question: {current_task_id}")
160
 
 
 
 
 
161
  response = self.agent_executor.invoke({"input": question, "task_id": current_task_id})
 
162
  final_answer = response.get("output", "No answer generated by agent.")
163
 
 
 
164
  final_answer = final_answer.strip()
165
+ # Puedes descomentar la siguiente línea si el LLM sigue añadiendo verbosidad
 
166
  # if final_answer.lower().startswith(("the answer is", "la respuesta es", "final answer:")):
167
  # final_answer = re.sub(r"^[Tt]he [Aa]nswer is: |^[Ll]a [Rr]espuesta es: |^[Ff]inal [Aa]nswer: ", "", final_answer, flags=re.IGNORECASE).strip()
168
 
 
170
  return final_answer
171
  except Exception as e:
172
  print(f"Error during agent execution: {e}")
 
 
173
  return f"ERROR: {e}"
174
 
175
+ # --- The `run_and_submit_all` function (originally from the template) ---
176
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
177
+ """
178
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
179
+ and displays the results.
180
+ """
181
+ # --- Determine HF Space Runtime URL and Repo URL ---
182
+ space_id = os.getenv("SPACE_ID")
183
+ if profile:
184
+ username = f"{profile.username}"
185
+ print(f"User logged in: {username}")
186
+ else:
187
+ print("User not logged in.")
188
+ return "Please Login to Hugging Face with the button.", None
189
+ api_url = DEFAULT_API_URL
190
+ questions_url = f"{api_url}/questions"
191
+ submit_url = f"{api_url}/submit"
192
+
193
+ # 1. Instantiate Agent ( modify this part to create your agent)
194
+ try:
195
+ agent = BasicAgent()
196
+ except Exception as e:
197
+ print(f"Error instantiating agent: {e}")
198
+ return f"Error initializing agent: {e}", None
199
+
200
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
201
+ print(agent_code)
202
+
203
+ # 2. Fetch Questions
204
+ print(f"Fetching questions from: {questions_url}")
205
+ try:
206
+ response = requests.get(questions_url, timeout=15)
207
+ response.raise_for_status()
208
+ questions_data = response.json()
209
+ if not questions_data:
210
+ print("Fetched questions list is empty.")
211
+ return "Fetched questions list is empty or invalid format.", None
212
+ print(f"Fetched {len(questions_data)} questions.")
213
+ except requests.exceptions.RequestException as e:
214
+ print(f"Error fetching questions: {e}")
215
+ return f"Error fetching questions: {e}", None
216
+ except requests.exceptions.JSONDecodeError as e:
217
+ print(f"Error decoding JSON response from questions endpoint: {e}")
218
+ print(f"Response text: {response.text[:500]}")
219
+ return f"Error decoding server response for questions: {e}", None
220
+ except Exception as e:
221
+ print(f"An unexpected error occurred fetching questions: {e}")
222
+ return f"An unexpected error occurred fetching questions: {e}", None
223
+
224
+ # 3. Run your Agent
225
+ results_log = []
226
+ answers_payload = []
227
+ print(f"Running agent on {len(questions_data)} questions...")
228
+ for item in questions_data:
229
+ task_id = item.get("task_id")
230
+ question_text = item.get("question")
231
+ if not task_id or question_text is None:
232
+ print(f"Skipping item with missing task_id or question: {item}")
233
+ continue
234
+ try:
235
+ submitted_answer = agent(question_text)
236
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
237
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
238
+ except Exception as e:
239
+ print(f"Error running agent on task {task_id}: {e}")
240
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
241
+
242
+ if not answers_payload:
243
+ print("Agent did not produce any answers to submit.")
244
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
245
+
246
+ # 4. Prepare Submission (THIS IS WHERE YOUR COPIED CODE GOES)
247
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
248
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
249
+ print(status_update)
250
+
251
+ # 5. Submit
252
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
253
+ try:
254
+ response = requests.post(submit_url, json=submission_data, timeout=60)
255
+ response.raise_for_status()
256
+ result_data = response.json()
257
+ final_status = (
258
+ f"Submission Successful!\n"
259
+ f"User: {result_data.get('username')}\n"
260
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
261
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
262
+ f"Message: {result_data.get('message', 'No message received.')}"
263
+ )
264
+ print("Submission successful.")
265
+ results_df = pd.DataFrame(results_log)
266
+ return final_status, results_df
267
+ except requests.exceptions.HTTPError as e:
268
+ error_detail = f"Server responded with status {e.response.status_code}."
269
+ try:
270
+ error_json = e.response.json()
271
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
272
+ except requests.exceptions.JSONDecodeError:
273
+ error_detail += f" Response: {e.response.text[:500]}"
274
+ status_message = f"Submission Failed: {error_detail}"
275
+ print(status_message)
276
+ results_df = pd.DataFrame(results_log)
277
+ return status_message, results_df
278
+ except requests.exceptions.Timeout:
279
+ status_message = "Submission Failed: The request timed out."
280
+ print(status_message)
281
+ results_df = pd.DataFrame(results_log)
282
+ return status_message, results_df
283
+ except requests.exceptions.RequestException as e:
284
+ status_message = f"Submission Failed: Network error - {e}"
285
+ print(status_message)
286
+ results_df = pd.DataFrame(results_log)
287
+ return status_message, results_df
288
+ except Exception as e:
289
+ status_message = f"An unexpected error occurred during submission: {e}"
290
+ print(status_message)
291
+ results_df = pd.DataFrame(results_log)
292
+ return status_message, results_df
293
+
294
+
295
+ # --- Build Gradio Interface using Blocks ---
296
+ with gr.Blocks() as demo:
297
+ gr.Markdown("# Basic Agent Evaluation Runner")
298
+ gr.Markdown(
299
+ """
300
+ **Instructions:**
301
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
302
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
303
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
304
+ ---
305
+ **Disclaimers:**
306
+ Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
307
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
308
+ """
309
+ )
310
+
311
+ gr.LoginButton()
312
+
313
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
314
+
315
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
316
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
317
+
318
+ run_button.click(
319
+ fn=run_and_submit_all,
320
+ outputs=[status_output, results_table]
321
+ )
322
+
323
+ if __name__ == "__main__":
324
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
325
+ space_host_startup = os.getenv("SPACE_HOST")
326
+ space_id_startup = os.getenv("SPACE_ID")
327
+
328
+ if space_host_startup:
329
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
330
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
331
+ else:
332
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
333
+
334
+ if space_id_startup:
335
+ print(f"✅ SPACE_ID found: {space_id_startup}")
336
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
337
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
338
+ else:
339
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
340
 
341
+ print("-"*(60 + len(" App Starting ")) + "\n")
342
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
343
+ demo.launch(debug=True, share=False)