albertCHY commited on
Commit
4a9da98
·
verified ·
1 Parent(s): f580e59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +233 -198
app.py CHANGED
@@ -1,222 +1,257 @@
 
1
  import os
2
- import gradio as gr
 
3
  import requests
4
- import inspect
5
- import pandas as pd
6
-
7
- import spaces
8
- from agent import graph
9
- from langchain_core.messages import HumanMessage, SystemMessage
10
-
11
- # (Keep Constants as is)
12
- # --- Constants ---
13
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
14
-
15
- # --- ZeroGPU Bypass ---
16
- # This dummy function tricks the Space into thinking we are using the GPU, preventing crashes.
17
- @spaces.GPU(duration=1)
18
- def gpu_check():
19
- return "GPU available"
20
-
21
- # --- Basic Agent Definition ---
22
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
23
-
24
-
25
-
26
- class BasicAgent:
27
- def __init__(self):
28
- print("BasicAgent initialized.")
29
- def __call__(self, question: str) -> str:
30
- response = graph.invoke({
31
- "messages": [SystemMessage(content = """
32
- You are a helpful assistant tasked with answering questions using a set of tools.
33
- Now, I will ask you a question. Report your thoughts, and answer in the fewest words possible.
34
- If the answer is only one word, format it as such, no extra punctuation.
35
- If your answer is a list, use a comma delimited list. Ex "cornstarch, freshly squeezed lemon juice, granulated sugar" or "Myanmar, Indonesia"
36
- If your answer is a number, only respond with the number, nothing else.
37
- DO NOT USE SQUARE BRACKETS IN YOUR ANSWERS UNLESS DIRECTLY STATED.
38
- DO NOT START YOUR ANSWER WITH "YOUR FINAL ANSWER".
39
- YOUR ANSWER SHOULD ONLY BE THE ANSWER AND NOTHING ELSE.
40
- """), HumanMessage(content=question)],
41
- "file_path": None,
42
- "url": None,
43
- "task_id": None
44
- })
45
- answer = response["messages"][-1].text
46
- return answer
47
-
48
- def run_and_submit_all( profile: gr.OAuthProfile | None):
49
  """
50
- Fetches all questions, runs the BasicAgent on them, submits all answers,
51
- and displays the results.
 
 
52
  """
53
- # --- Determine HF Space Runtime URL and Repo URL ---
54
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
55
 
56
- if profile:
57
- username= f"{profile.username}"
58
- print(f"User logged in: {username}")
59
- else:
60
- print("User not logged in.")
61
- return "Please Login to Hugging Face with the button.", None
 
 
 
62
 
63
- api_url = DEFAULT_API_URL
64
- questions_url = f"{api_url}/questions"
65
- submit_url = f"{api_url}/submit"
 
 
 
 
 
 
66
 
67
- # 1. Instantiate Agent ( modify this part to create your agent)
68
- try:
69
- agent = BasicAgent()
70
- except Exception as e:
71
- print(f"Error instantiating agent: {e}")
72
- return f"Error initializing agent: {e}", None
73
- # 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)
74
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
75
- print(agent_code)
76
-
77
- # 2. Fetch Questions
78
- print(f"Fetching questions from: {questions_url}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  try:
80
- response = requests.get(questions_url, timeout=15)
81
- response.raise_for_status()
82
- questions_data = response.json()
83
- if not questions_data:
84
- print("Fetched questions list is empty.")
85
- return "Fetched questions list is empty or invalid format.", None
86
- print(f"Fetched {len(questions_data)} questions.")
87
- except requests.exceptions.RequestException as e:
88
- print(f"Error fetching questions: {e}")
89
- return f"Error fetching questions: {e}", None
90
- except requests.exceptions.JSONDecodeError as e:
91
- print(f"Error decoding JSON response from questions endpoint: {e}")
92
- print(f"Response text: {response.text[:500]}")
93
- return f"Error decoding server response for questions: {e}", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  except Exception as e:
95
- print(f"An unexpected error occurred fetching questions: {e}")
96
- return f"An unexpected error occurred fetching questions: {e}", None
97
-
98
- # 3. Run your Agent
99
- results_log = []
100
- answers_payload = []
101
- print(f"Running agent on {len(questions_data)} questions...")
102
- for item in questions_data:
103
- task_id = item.get("task_id")
104
- question_text = item.get("question")
105
- if not task_id or question_text is None:
106
- print(f"Skipping item with missing task_id or question: {item}")
107
- continue
108
- try:
109
- submitted_answer = agent(question_text)
110
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
111
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
112
- except Exception as e:
113
- print(f"Error running agent on task {task_id}: {e}")
114
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
115
-
116
- if not answers_payload:
117
- print("Agent did not produce any answers to submit.")
118
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
119
-
120
- # 4. Prepare Submission
121
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
122
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
123
- print(status_update)
124
-
125
- # 5. Submit
126
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
127
  try:
128
- response = requests.post(submit_url, json=submission_data, timeout=60)
 
129
  response.raise_for_status()
130
- result_data = response.json()
131
- final_status = (
132
- f"Submission Successful!\n"
133
- f"User: {result_data.get('username')}\n"
134
- f"Overall Score: {result_data.get('score', 'N/A')}% "
135
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
136
- f"Message: {result_data.get('message', 'No message received.')}"
137
- )
138
- print("Submission successful.")
139
- results_df = pd.DataFrame(results_log)
140
- return final_status, results_df
141
- except requests.exceptions.HTTPError as e:
142
- error_detail = f"Server responded with status {e.response.status_code}."
143
- try:
144
- error_json = e.response.json()
145
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
146
- except requests.exceptions.JSONDecodeError:
147
- error_detail += f" Response: {e.response.text[:500]}"
148
- status_message = f"Submission Failed: {error_detail}"
149
- print(status_message)
150
- results_df = pd.DataFrame(results_log)
151
- return status_message, results_df
152
- except requests.exceptions.Timeout:
153
- status_message = "Submission Failed: The request timed out."
154
- print(status_message)
155
- results_df = pd.DataFrame(results_log)
156
- return status_message, results_df
157
- except requests.exceptions.RequestException as e:
158
- status_message = f"Submission Failed: Network error - {e}"
159
- print(status_message)
160
- results_df = pd.DataFrame(results_log)
161
- return status_message, results_df
162
- except Exception as e:
163
- status_message = f"An unexpected error occurred during submission: {e}"
164
- print(status_message)
165
- results_df = pd.DataFrame(results_log)
166
- return status_message, results_df
167
 
 
 
 
 
 
 
168
 
169
- # --- Build Gradio Interface using Blocks ---
170
- with gr.Blocks() as demo:
171
- gr.Markdown("# Basic Agent Evaluation Runner")
172
- gr.Markdown(
173
- """
174
- **Instructions:**
175
 
176
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
177
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
178
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
179
 
180
- ---
181
- **Disclaimers:**
182
- 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).
183
- 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.
184
- """
185
- )
186
 
187
- gr.LoginButton()
 
 
 
 
188
 
189
- run_button = gr.Button("Run Evaluation & Submit All Answers")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
192
- # Removed max_rows=10 from DataFrame constructor
193
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
194
 
195
- run_button.click(
196
- fn=run_and_submit_all,
197
- outputs=[status_output, results_table]
198
- )
 
 
 
 
 
 
199
 
200
- if __name__ == "__main__":
201
- print("\n" + "-"*30 + " App Starting " + "-"*30)
202
- # Check for SPACE_HOST and SPACE_ID at startup for information
203
- space_host_startup = os.getenv("SPACE_HOST")
204
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
 
 
 
 
 
205
 
206
- if space_host_startup:
207
- print(f"✅ SPACE_HOST found: {space_host_startup}")
208
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
209
- else:
210
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
211
 
212
- if space_id_startup: # Print repo URLs if SPACE_ID is found
213
- print(f"✅ SPACE_ID found: {space_id_startup}")
214
- print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
215
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
216
- else:
217
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
218
 
219
- print("-"*(60 + len(" App Starting ")) + "\n")
 
 
220
 
221
- print("Launching Gradio Interface for Basic Agent Evaluation...")
222
- demo.launch(debug=True, share=False)
 
1
+ import base64
2
  import os
3
+ import io
4
+ import contextlib
5
  import requests
6
+ from typing import TypedDict, Annotated
7
+ from langchain_core.messages import HumanMessage, AnyMessage
8
+ from langgraph.graph import START, StateGraph, add_messages
9
+ from langgraph.prebuilt import ToolNode, tools_condition
10
+ from langchain_community.tools import tool, DuckDuckGoSearchRun
11
+ from langchain_community.document_loaders import WikipediaLoader
12
+ from langchain_google_genai import ChatGoogleGenerativeAI
13
+ from pathlib import Path
14
+ import tempfile
15
+ from dotenv import load_dotenv
16
+
17
+ # constants
18
+ API_URL = "https://agents-course-unit4-scoring.hf.space"
19
+ QUESTIONS_URL = f"{API_URL}/questions"
20
+ FILES_URL = f"{API_URL}/files"
21
+ SUBMIT_URL = f"{API_URL}/submit"
22
+ load_dotenv()
23
+
24
+ class AgentState(TypedDict):
25
+ messages: Annotated[list[AnyMessage], add_messages]
26
+ file_path: str | None
27
+ task_id: str | None
28
+ url: str | None
29
+
30
+ def build_gemini_llm():
31
+ if not os.environ.get("GOOGLE_API_KEY"):
32
+ raise ValueError("GOOGLE_API_KEY environment variable is not set.")
33
+ return ChatGoogleGenerativeAI(model = "gemini-3.7-flash", temperature = 0, max_output_tokens = 1025, include_thoughts=True)
34
+
35
+ @tool
36
+ def add_numbers(a: int, b: int) -> int:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  """
38
+ Adds two numbers and return the result.
39
+ Args:
40
+ a (int)
41
+ b (int)
42
  """
43
+ return a + b
 
44
 
45
+ @tool
46
+ def subtract_numbers(a: int, b: int) -> int:
47
+ """
48
+ Subtracts the second number from the first and return the result.
49
+ Args:
50
+ a (int)
51
+ b (int)
52
+ """
53
+ return a - b
54
 
55
+ @tool
56
+ def multiply_numbers(a: int, b: int) -> int:
57
+ """
58
+ Multiplies two numbers and return the result.
59
+ Args:
60
+ a (int)
61
+ b (int)
62
+ """
63
+ return a * b
64
 
65
+ @tool
66
+ def divide_numbers(a: int, b: int) -> float:
67
+ """
68
+ Divides the first number by the second and return the result.
69
+ Args:
70
+ a (int)
71
+ b (int)
72
+ """
73
+ return a / b
74
+
75
+ @tool
76
+ def search_web(query:str) -> str:
77
+ """
78
+ Searches the web for the answer to a given question or topic.
79
+ Args:
80
+ query (str): the question or topic to search for.
81
+ """
82
+ return DuckDuckGoSearchRun().run(query)
83
+
84
+ @tool
85
+ def extract_text_from_image(img_path: str) -> str:
86
+ """
87
+ Describe the image and extract any text in it.
88
+ Args:
89
+ img_path (str): the path to the image file.
90
+ """
91
+ all_text = ""
92
  try:
93
+ # Read image and encode as base64
94
+ with open(img_path, "rb") as image_file:
95
+ image_bytes = image_file.read()
96
+
97
+ image_base64 = base64.b64encode(image_bytes).decode("utf-8")
98
+
99
+ # Prepare the prompt including the base64 image data
100
+ message = [
101
+ HumanMessage(
102
+ content=[
103
+ {
104
+ "type": "text",
105
+ "text": (
106
+ "Describe the image and extract any text in it."
107
+ ),
108
+ },
109
+ {
110
+ "type": "image_url",
111
+ "image_url": {
112
+ "url": f"data:image/png;base64,{image_base64}"
113
+ },
114
+ },
115
+ ]
116
+ )
117
+ ]
118
+ response = model.invoke(message)
119
+ # Append extracted text
120
+ all_text += response.text + "\n\n"
121
+ return all_text.strip()
122
  except Exception as e:
123
+ # A butler should handle errors gracefully
124
+ error_msg = f"Error extracting text: {str(e)}"
125
+ print(error_msg)
126
+ return ""
127
+
128
+ @tool
129
+ def download_and_read_file(task_id: str) -> str:
130
+ """
131
+ Download and read the file attached to the GAIA task its contents.
132
+ Always call this first if there is a file attached to a GAIA Task.
133
+ Args:
134
+ task_id (str): The ID of the GAIA task.
135
+ Returns:
136
+ str: The contents of the file as a string.
137
+ """
138
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  try:
140
+ # Download the file from the GAIA API
141
+ response = requests.get(f"{FILES_URL}/{task_id}", timeout = 10)
142
  response.raise_for_status()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
+ # Determine the file type and read its contents
145
+ content_disposition = response.headers.get("content-disposition", "")
146
+ content_type = response.headers.get("content-type", "")
147
+ filename = None
148
+ if "filename=" in content_disposition:
149
+ filename = content_disposition.split("filename=")[1].strip('"')
150
 
151
+ if not filename:
152
+ filename = f"{task_id}.bin"
 
 
 
 
153
 
154
+ ext = Path(filename).suffix.lower()
 
 
155
 
156
+ if ext in(".txt", ".py", ".json", ".md", ".ymal", ".html", ".xml", ""):
157
+ return response.text
 
 
 
 
158
 
159
+ if ext == ".xlsx" or "xlsx" in content_type:
160
+ import pandas as pd
161
+ with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as file:
162
+ file.write(response.content)
163
+ temp_path = file.name
164
 
165
+ read_file = pd.read_excel(temp_path)
166
+ return read_file.to_string()
167
+ if ext == ".csv" or "csv" in content_type:
168
+ import pandas as pd
169
+ with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as file:
170
+ file.write(response.content)
171
+ temp_path = file.name
172
+ read_file = pd.read_csv(temp_path)
173
+ return read_file.to_string()
174
+ if ext == ".csv" or "csv" in content_type:
175
+ import pandas as pd
176
+ with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as file:
177
+ file.write(response.content)
178
+ temp_path = file.name
179
+ read_file = pd.read_csv(temp_path)
180
+ return read_file.to_string()
181
+ if ext == ".jpg" or ext == ".jpeg" or ext == ".png" or "image" in content_type:
182
+ from PIL import Image
183
+ with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as file:
184
+ file.write(response.content)
185
+ temp_path = file.name
186
+ return extract_text_from_image(temp_path)
187
+
188
+ # Unsupported file type
189
+ return (
190
+ f"Unsupported file type: {content_type}. "
191
+ "I downloaded the file successfully, but I don't know "
192
+ "how to extract its contents."
193
+ )
194
+ except requests.RequestException as e:
195
+ return f"Failed to download file: {e}"
196
+ except Exception as e: return f"Failed to read file: {e}"
197
+
198
+ except Exception as e:
199
+ return f"error downloading or reading file: {str(e)}"
200
+
201
+ return file_content
202
+
203
+ @tool
204
+ def wikipedia_search(query: str):
205
+ """
206
+ Search wikipedia for a query and return a max of three results.
207
+ Takes a string query as the search query
208
+ Args:
209
+ query (str): The search query.
210
+ """
211
+ try:
212
+ search_results = WikipediaLoader(query=query, load_max_docs=3).load()
213
+
214
+ if not search_results:
215
+ return f"No Wikipedia results found for {query}. Consider another query or try a web search."
216
+ return "\n\n---\n\n".join(
217
+ f"Title: {doc.metadata.get('title', 'Unknown')}\n"
218
+ f"Content: {doc.page_content}"
219
+ for doc in search_results
220
+ )
221
+ except Exception as e:
222
+ print(f"Wikipedia search failed for {query}: {e}")
223
+ return f"Wikipedia search failed for {query}. Try a web search instead."
224
 
 
 
 
225
 
226
+ model = build_gemini_llm()
227
+ tools = [
228
+ add_numbers,
229
+ subtract_numbers,
230
+ multiply_numbers,
231
+ divide_numbers,
232
+ extract_text_from_image,
233
+ search_web
234
+ ]
235
+ model_with_tools = model.bind_tools(tools)
236
 
237
+ def assistant(state: AgentState):
238
+ response = model_with_tools.invoke(state["messages"])
239
+ reasoning_tokens = response.usage_metadata["output_token_details"]["reasoning"]
240
+ print("Reasoning tokens used:", reasoning_tokens)
241
+ return {
242
+ "messages": [response],
243
+ "file_path": state["file_path"],
244
+ "task_id": state["task_id"],
245
+ "url": state["url"]
246
+ }
247
 
248
+ builder = StateGraph(AgentState)
 
 
 
 
249
 
250
+ builder.add_node("assistant", assistant)
251
+ builder.add_node("tools", ToolNode(tools))
 
 
 
 
252
 
253
+ builder.add_edge(START, "assistant")
254
+ builder.add_conditional_edges("assistant", tools_condition)
255
+ builder.add_edge("tools", "assistant")
256
 
257
+ graph = builder.compile()