byArMan456 commited on
Commit
903329c
·
verified ·
1 Parent(s): 28965fc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -177
app.py CHANGED
@@ -3,8 +3,24 @@ import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # (Keep Constants as is)
8
  # --- Constants ---
9
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
10
 
@@ -13,182 +29,185 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
  class BasicAgent:
14
  def __init__(self):
15
  print("BasicAgent initialized.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
21
-
22
- def run_and_submit_all( profile: gr.OAuthProfile | None):
23
- """
24
- Fetches all questions, runs the BasicAgent on them, submits all answers,
25
- and displays the results.
26
- """
27
- # --- Determine HF Space Runtime URL and Repo URL ---
28
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
29
-
30
- if profile:
31
- username= f"{profile.username}"
32
- print(f"User logged in: {username}")
33
- else:
34
- print("User not logged in.")
35
- return "Please Login to Hugging Face with the button.", None
36
-
37
- api_url = DEFAULT_API_URL
38
- questions_url = f"{api_url}/questions"
39
- submit_url = f"{api_url}/submit"
40
-
41
- # 1. Instantiate Agent ( modify this part to create your agent)
42
- try:
43
- agent = BasicAgent()
44
- except Exception as e:
45
- print(f"Error instantiating agent: {e}")
46
- return f"Error initializing agent: {e}", None
47
- # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
48
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
49
- print(agent_code)
50
-
51
- # 2. Fetch Questions
52
- print(f"Fetching questions from: {questions_url}")
53
- try:
54
- response = requests.get(questions_url, timeout=15)
55
- response.raise_for_status()
56
- questions_data = response.json()
57
- if not questions_data:
58
- print("Fetched questions list is empty.")
59
- return "Fetched questions list is empty or invalid format.", None
60
- print(f"Fetched {len(questions_data)} questions.")
61
- except requests.exceptions.RequestException as e:
62
- print(f"Error fetching questions: {e}")
63
- return f"Error fetching questions: {e}", None
64
- except requests.exceptions.JSONDecodeError as e:
65
- print(f"Error decoding JSON response from questions endpoint: {e}")
66
- print(f"Response text: {response.text[:500]}")
67
- return f"Error decoding server response for questions: {e}", None
68
- except Exception as e:
69
- print(f"An unexpected error occurred fetching questions: {e}")
70
- return f"An unexpected error occurred fetching questions: {e}", None
71
-
72
- # 3. Run your Agent
73
- results_log = []
74
- answers_payload = []
75
- print(f"Running agent on {len(questions_data)} questions...")
76
- for item in questions_data:
77
- task_id = item.get("task_id")
78
- question_text = item.get("question")
79
- if not task_id or question_text is None:
80
- print(f"Skipping item with missing task_id or question: {item}")
81
- continue
82
  try:
83
- submitted_answer = agent(question_text)
84
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
85
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  except Exception as e:
87
- print(f"Error running agent on task {task_id}: {e}")
88
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
89
-
90
- if not answers_payload:
91
- print("Agent did not produce any answers to submit.")
92
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
93
-
94
- # 4. Prepare Submission
95
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
96
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
97
- print(status_update)
98
-
99
- # 5. Submit
100
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
101
- try:
102
- response = requests.post(submit_url, json=submission_data, timeout=60)
103
- response.raise_for_status()
104
- result_data = response.json()
105
- final_status = (
106
- f"Submission Successful!\n"
107
- f"User: {result_data.get('username')}\n"
108
- f"Overall Score: {result_data.get('score', 'N/A')}% "
109
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
110
- f"Message: {result_data.get('message', 'No message received.')}"
111
- )
112
- print("Submission successful.")
113
- results_df = pd.DataFrame(results_log)
114
- return final_status, results_df
115
- except requests.exceptions.HTTPError as e:
116
- error_detail = f"Server responded with status {e.response.status_code}."
117
- try:
118
- error_json = e.response.json()
119
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
120
- except requests.exceptions.JSONDecodeError:
121
- error_detail += f" Response: {e.response.text[:500]}"
122
- status_message = f"Submission Failed: {error_detail}"
123
- print(status_message)
124
- results_df = pd.DataFrame(results_log)
125
- return status_message, results_df
126
- except requests.exceptions.Timeout:
127
- status_message = "Submission Failed: The request timed out."
128
- print(status_message)
129
- results_df = pd.DataFrame(results_log)
130
- return status_message, results_df
131
- except requests.exceptions.RequestException as e:
132
- status_message = f"Submission Failed: Network error - {e}"
133
- print(status_message)
134
- results_df = pd.DataFrame(results_log)
135
- return status_message, results_df
136
- except Exception as e:
137
- status_message = f"An unexpected error occurred during submission: {e}"
138
- print(status_message)
139
- results_df = pd.DataFrame(results_log)
140
- return status_message, results_df
141
-
142
-
143
- # --- Build Gradio Interface using Blocks ---
144
- with gr.Blocks() as demo:
145
- gr.Markdown("# Basic Agent Evaluation Runner")
146
- gr.Markdown(
147
- """
148
- **Instructions:**
149
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
150
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
151
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
152
- ---
153
- **Disclaimers:**
154
- 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).
155
- 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.
156
- """
157
- )
158
-
159
- gr.LoginButton()
160
-
161
- run_button = gr.Button("Run Evaluation & Submit All Answers")
162
-
163
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
164
- # Removed max_rows=10 from DataFrame constructor
165
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
166
-
167
- run_button.click(
168
- fn=run_and_submit_all,
169
- outputs=[status_output, results_table]
170
- )
171
-
172
- if __name__ == "__main__":
173
- print("\n" + "-"*30 + " App Starting " + "-"*30)
174
- # Check for SPACE_HOST and SPACE_ID at startup for information
175
- space_host_startup = os.getenv("SPACE_HOST")
176
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
177
-
178
- if space_host_startup:
179
- print(f"✅ SPACE_HOST found: {space_host_startup}")
180
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
181
- else:
182
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
183
-
184
- if space_id_startup: # Print repo URLs if SPACE_ID is found
185
- print(f"✅ SPACE_ID found: {space_id_startup}")
186
- print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
187
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
188
- else:
189
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
190
-
191
- print("-"*(60 + len(" App Starting ")) + "\n")
192
-
193
- print("Launching Gradio Interface for Basic Agent Evaluation...")
194
- demo.launch(debug=True, share=False)
 
3
  import requests
4
  import inspect
5
  import pandas as pd
6
+ from typing import List, Dict
7
+
8
+ # --- Importar las librerías necesarias para el agente ---
9
+ # Para el LLM de Google
10
+ from langchain_google_genai import ChatGoogleGenerativeAI # Para modelos de chat como Gemini
11
+ # Para construir el agente
12
+ from langchain.agents import AgentExecutor, create_react_agent
13
+ from langchain import hub # Para jalar prompts estándar
14
+ from langchain.tools import Tool # Para envolver tus funciones como herramientas
15
+ from langchain_core.prompts import PromptTemplate # Para personalizar el prompt
16
+ # Para la herramienta de búsqueda web
17
+ 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
 
 
24
  # --- Constants ---
25
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
26
 
 
29
  class BasicAgent:
30
  def __init__(self):
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
+
38
+ if not google_api_key:
39
+ raise ValueError("GOOGLE_API_KEY environment variable not set. Please add it to your Hugging Face Space secrets.")
40
+ if not tavily_api_key:
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()
90
+ doc.close()
91
+ except ImportError:
92
+ content = "Error: pymupdf not installed for PDF reading."
93
+ except Exception as e:
94
+ content = f"Error reading PDF: {e}"
95
+ elif filename.lower().endswith((".csv", ".txt", ".json", ".log", ".md")):
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:
111
+ return f"An unexpected error occurred processing file '{filename}': {e}"
112
+
113
+ self.tools = [
114
+ Tool(
115
+ name="TavilySearch",
116
+ func=self.tavily_search_tool.run,
117
+ description="Útil para buscar información general, hechos, noticias y definiciones en internet. Siempre usa esta herramienta cuando necesites información externa.",
118
+ ),
119
+ Tool(
120
+ name="DownloadAndReadTaskFile",
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:
140
+ - Si la pregunta requiere un número, solo devuelve el número (ej: 12345).
141
+ - Si requiere una fecha, solo la fecha en el formato solicitado o más común (ej: 2023-10-26 o October 26, 2023).
142
+ - Si requiere un nombre, solo el nombre (ej: París).
143
+ - Si requiere una lista, solo la lista de elementos separados por comas o líneas, sin numeración ni viñetas, a menos que se especifique lo contrario (ej: Manzanas, Peras, Uvas).
144
+ - NO incluyas ninguna explicación, introducción, despedida, o texto adicional.
145
+ - Utiliza las herramientas disponibles cuando sea necesario para encontrar la información.
146
+ - Piensa paso a paso y sé muy preciso en tu razonamiento.
147
+ - Si la pregunta menciona un archivo, utiliza la herramienta `DownloadAndReadTaskFile` con el `task_id` y el nombre del archivo.
148
+
149
+ Question: {input}
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
+
201
+ print(f"Agent returning final answer: '{final_answer}'")
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