ayush1603 commited on
Commit
e6ebc66
Β·
verified Β·
1 Parent(s): 7146778

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +345 -128
app.py CHANGED
@@ -1,24 +1,24 @@
 
1
  import os
2
- import re
3
- import tempfile
4
- from pathlib import Path
5
- from typing import Optional
6
-
7
  import requests
 
 
 
 
 
 
 
8
  import pandas as pd
9
- import PyPDF2
10
- import pytesseract
11
- from PIL import Image
12
-
13
- import gradio as gr
14
- import litellm
15
- from opik import track
16
  from litellm.integrations.opik.opik import OpikLogger
17
 
18
- from smolagents import tool, LiteLLMModel, CodeAgent , PythonInterpreterTool,DuckDuckGoSearchTool, WikipediaSearchTool, SpeechToTextTool
 
 
19
 
20
-
21
- # ── 1) Read API keys from HF Spaces Secrets/Variables ─────────────────────────
22
  GROQ_API_KEY = os.getenv("Grok_api") # set as Secret in your Space
23
  OPIK_API_KEY = os.getenv("OPIK_API_KEY") # set as Secret in your Space
24
  OPIK_WORKSPACE = os.getenv("OPIK_WORKSPACE") # set as Variable in your Space
@@ -28,36 +28,129 @@ os.environ["GROQ_API_KEY"] = GROQ_API_KEY
28
  os.environ["OPIK_API_KEY"] = OPIK_API_KEY
29
  os.environ["OPIK_WORKSPACE"] = OPIK_WORKSPACE
30
 
31
- litellm.callbacks = [OpikLogger()]
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- # ── 3) Create a Groq-backed LiteLLMModel ─────────────────────────────────────
34
- llm = LiteLLMModel(
35
- model_id="groq/llama-3.3-70b-versatile",
36
- client=litellm
37
- )
 
 
38
 
39
- # ── 4) Define function-based tools with @tool ─────────────────────────────────
40
- @tool
41
- def excel_to_text_tool(excel_path: str, sheet_name: Optional[str] = None) -> str:
42
- """
43
- Read an Excel file and return a Markdown table of the specified sheet.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- Args:
46
- excel_path: Path to the Excel file
47
- sheet_name: sheets about the data
48
-
49
- Returns:
50
- Analysis result or error message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  """
52
- path = Path(excel_path).expanduser().resolve()
53
- if not path.exists():
54
- return f"Error: Excel file not found at {path}"
 
 
 
55
  try:
56
- sheet = 0 if sheet_name is None else (int(sheet_name) if sheet_name.isdigit() else sheet_name)
57
- df = pd.read_excel(path, sheet_name=sheet)
58
- return df.to_markdown(index=False)
59
- except Exception as e:
60
- return f"Error reading Excel file: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  @tool
63
  def pdf_to_text_tool(pdf_path: str) -> str:
@@ -102,100 +195,224 @@ def analyze_image_tool(image_path: str) -> str:
102
  except Exception as e:
103
  return f"Error analyzing image: {e}"
104
 
105
- # ── 5) Build worker and manager CodeAgents ───────────────────────────────────
106
- worker_agent = CodeAgent(
107
- model=llm,
108
- tools=[
109
- DuckDuckGoSearchTool(),
110
- WikipediaSearchTool(),
111
- SpeechToTextTool(),
112
- excel_to_text_tool,
113
- pdf_to_text_tool,
114
- analyze_image_tool,
115
- PythonInterpreterTool()
116
- ],
117
- add_base_tools=True,
118
- name="worker_agent",
119
- additional_authorized_imports=['pandas','numpy','csv','subprocess'],
120
- description="Handles web, Excel, PDF, and image analysis tasks",
121
- max_steps=15,
122
- verbosity_level=1
123
- )
124
 
125
- manager_agent = CodeAgent(
126
- model=llm,
127
- tools=[],
128
- managed_agents=[worker_agent],
129
- additional_authorized_imports=["pandas", "matplotlib.pyplot as plt"],
130
- planning_interval=5,
131
- name="manager_agent",
132
- description="Orchestrates complex workflows via worker_agent",
133
- verbosity_level=2
134
  )
135
 
136
- # ── 6) Helper to download attached files ─────────────────────────────────────
137
- def download_file_if_any(base_api_url: str, task_id: str) -> Optional[str]:
138
- url = f"{base_api_url}/files/{task_id}"
139
- resp = requests.get(url, timeout=30)
140
- if resp.status_code == 404:
141
- return None
142
- resp.raise_for_status()
143
- fname = task_id
144
- cd = resp.headers.get("content-disposition", "")
145
- m = re.search(r'filename="([^"]+)"', cd)
146
- if m:
147
- fname = m.group(1)
148
- tmp = Path(tempfile.gettempdir()) / "gaia_files"
149
- tmp.mkdir(exist_ok=True)
150
- fp = tmp / fname
151
- fp.write_bytes(resp.content)
152
- return str(fp)
153
-
154
- # ── 7) Gradio runner callback ─────────────────────────────────────────────────
155
- def run_and_submit_all(profile: gr.OAuthProfile | None, agent_choice: str):
156
- if profile is None:
157
- return "Please login to Hugging Face.", None
158
- username = profile.username
159
-
160
- resp = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15)
161
- resp.raise_for_status()
162
- questions = resp.json()
163
-
164
- results, answers = [], []
165
- for item in questions:
166
- tid, q = item["task_id"], item["question"]
167
- fpath = download_file_if_any(DEFAULT_API_URL, tid)
168
- prompt = q + (f"\n\n---\nFile: {fpath}\n---\n" if fpath else "")
169
- agent = worker_agent if agent_choice == "worker" else manager_agent
170
- ans = agent.run(prompt)
171
- results.append({"Task ID": tid, "Question": q, "Answer": ans})
172
- answers.append({"task_id": tid, "submitted_answer": ans})
173
-
174
- payload = {
175
- "username": username,
176
- "agent_code": "https://huggingface.co/spaces/l3xv/Final_Assignment_Template/tree/main",
177
- "answers": answers
178
- }
179
- sub = requests.post(f"{DEFAULT_API_URL}/submit", json=payload, timeout=60)
180
- sub.raise_for_status()
181
- data = sub.json()
182
- status = f"Score: {data['score']}% ({data['correct_count']}/{data['total_attempted']})"
183
- return status, pd.DataFrame(results)
184
 
185
- # ── 8) Gradio UI wiring ─────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  with gr.Blocks() as demo:
187
- gr.Markdown("## Multi-Agent Runner (Groq-only LLM)")
188
- profile = gr.LoginButton()
189
- agent_choice = gr.Radio(["manager", "worker"], label="Agent to use?", value="manager")
190
- run_btn = gr.Button("Run & Submit")
191
- out_status = gr.Textbox(label="Status", interactive=False)
192
- out_table = gr.DataFrame(label="Results")
193
-
194
- run_btn.click(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  fn=run_and_submit_all,
196
- inputs=[profile, agent_choice],
197
- outputs=[out_status, out_table]
198
  )
199
 
200
  if __name__ == "__main__":
201
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
  import os
3
+ import gradio as gr
 
 
 
 
4
  import requests
5
+ import openai
6
+ from smolagents import DuckDuckGoSearchTool, CodeAgent, WikipediaSearchTool , LiteLLMModel, CodeAgent
7
+ from pathlib import Path
8
+ import tempfile
9
+ from smolagents.tools import PipelineTool, Tool
10
+ import pathlib
11
+ from typing import Union, Optional
12
  import pandas as pd
13
+ from tabulate import tabulate # pragma: no cover – fallback path
14
+ import re
15
+ import opik
 
 
 
 
16
  from litellm.integrations.opik.opik import OpikLogger
17
 
18
+ # (Keep Constants as is)
19
+ # --- Constants ---
20
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
21
 
 
 
22
  GROQ_API_KEY = os.getenv("Grok_api") # set as Secret in your Space
23
  OPIK_API_KEY = os.getenv("OPIK_API_KEY") # set as Secret in your Space
24
  OPIK_WORKSPACE = os.getenv("OPIK_WORKSPACE") # set as Variable in your Space
 
28
  os.environ["OPIK_API_KEY"] = OPIK_API_KEY
29
  os.environ["OPIK_WORKSPACE"] = OPIK_WORKSPACE
30
 
31
+ # ──────────────────────────────────────────────────────────────────────────
32
+ # Internal helpers
33
+ # ──────────────────────────────────────────────────────────────────────────
34
+ @staticmethod
35
+ def _transcribe(audio_path: str) -> str:
36
+ # ----- validation ----------------------------------------------------
37
+ if not isinstance(audio_path, str):
38
+ raise TypeError(
39
+ "Parameter 'audio' must be a string containing the file path."
40
+ )
41
+ path = Path(audio_path).expanduser().resolve()
42
+ if not path.is_file():
43
+ raise FileNotFoundError(f"No such audio file: {path}")
44
 
45
+ # ----- API call ------------------------------------------------------
46
+ with path.open("rb") as fp:
47
+ response = openai.audio.transcriptions.create(
48
+ file=fp,
49
+ model="whisper-1", # currently the only Whisper model
50
+ response_format="text" # returns plain text instead of JSON
51
+ )
52
 
53
+ # For response_format="text", `response` is already the raw transcript
54
+ return response
55
+
56
+ class ExcelToTextTool(Tool):
57
+ """Render an Excel worksheet as Markdown text."""
58
+
59
+ # ------------------------------------------------------------------
60
+ # Required smol‑agents metadata
61
+ # ------------------------------------------------------------------
62
+ name = "excel_to_text"
63
+ description = (
64
+ "Read an Excel file and return a Markdown table of the requested sheet. "
65
+ "Accepts either the sheet name or the zero-based index."
66
+ )
67
+
68
+ inputs = {
69
+ "excel_path": {
70
+ "type": "string",
71
+ "description": "Path to the Excel file (.xlsx / .xls).",
72
+ },
73
+ "sheet_name": {
74
+ "type": "string",
75
+ "description": (
76
+ "Worksheet name or zero‑based index *as a string* (optional; default first sheet)."
77
+ ),
78
+ "nullable": True,
79
+ },
80
+ }
81
+
82
+ output_type = "string"
83
 
84
+
85
+ # ------------------------------------------------------------------
86
+ # Core logic
87
+ # ------------------------------------------------------------------
88
+ def forward(
89
+ self,
90
+ excel_path: str,
91
+ sheet_name: Optional[str] = None,
92
+ ) -> str:
93
+ """Load *excel_path* and return the sheet as a Markdown table."""
94
+
95
+ path = pathlib.Path(excel_path).expanduser().resolve()
96
+ if not path.exists():
97
+ return f"Error: Excel file not found at {path}"
98
+
99
+ try:
100
+ # Interpret sheet identifier -----------------------------------
101
+ sheet: Union[str, int]
102
+ if sheet_name is None or sheet_name == "":
103
+ sheet = 0 # first sheet
104
+ else:
105
+ # If the user passed a numeric string (e.g. "1"), cast to int
106
+ sheet = int(sheet_name) if sheet_name.isdigit() else sheet_name
107
+
108
+ # Load worksheet ----------------------------------------------
109
+ df = pd.read_excel(path, sheet_name=sheet)
110
+
111
+ # Render to Markdown; fall back to tabulate if needed ---------
112
+ if hasattr(pd.DataFrame, "to_markdown"):
113
+ return df.to_markdown(index=False)
114
+ from tabulate import tabulate # pragma: no cover – fallback path
115
+
116
+ return tabulate(df, headers="keys", tablefmt="github", showindex=False)
117
+
118
+ except Exception as exc: # broad catch keeps the agent chat‑friendly
119
+ return f"Error reading Excel file: {exc}"
120
+
121
+
122
+ def download_file_if_any(base_api_url: str, task_id: str) -> str | None:
123
  """
124
+ Try GET /files/{task_id}.
125
+ β€’ On HTTP 200 β†’ save to a temp dir and return local path.
126
+ β€’ On 404 β†’ return None.
127
+ β€’ On other errors β†’ raise so caller can log / handle.
128
+ """
129
+ url = f"{base_api_url}/files/{task_id}"
130
  try:
131
+ resp = requests.get(url, timeout=30)
132
+ if resp.status_code == 404:
133
+ return None # no file
134
+ resp.raise_for_status() # raise on 4xx/5xx β‰  404
135
+ except requests.exceptions.HTTPError as e:
136
+ # propagate non-404 errors (403, 500, …)
137
+ raise e
138
+
139
+ # β–Έ Save bytes to a named file inside the system temp dir
140
+ # Try to keep original extension from Content-Disposition if present.
141
+ cdisp = resp.headers.get("content-disposition", "")
142
+ filename = task_id # default base name
143
+ if "filename=" in cdisp:
144
+ m = re.search(r'filename="([^"]+)"', cdisp)
145
+ if m:
146
+ filename = m.group(1) # keep provided name
147
+
148
+ tmp_dir = Path(tempfile.gettempdir()) / "gaia_files"
149
+ tmp_dir.mkdir(exist_ok=True)
150
+ file_path = tmp_dir / filename
151
+ with open(file_path, "wb") as f:
152
+ f.write(resp.content)
153
+ return str(file_path)
154
 
155
  @tool
156
  def pdf_to_text_tool(pdf_path: str) -> str:
 
195
  except Exception as e:
196
  return f"Error analyzing image: {e}"
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
+ litellm.callbacks = [OpikLogger()]
200
+ llm = LiteLLMModel(
201
+ model_id="groq/llama-3.3-70b-versatile",
202
+ client=litellm
 
 
 
 
 
203
  )
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
+ # --- Basic Agent Definition ---
207
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
208
+ @track
209
+ class BasicAgent:
210
+ def __init__(self):
211
+ self.agent = CodeAgent(
212
+ model=llm,
213
+ tools=[DuckDuckGoSearchTool(), WikipediaSearchTool(), SpeechToTextTool(), ExcelToTextTool() , pdf_to_text_tool, analyze_image_tool],
214
+ add_base_tools=True,
215
+ additional_authorized_imports=['pandas','numpy','csv','subprocess']
216
+ )
217
+
218
+ print("BasicAgent initialized.")
219
+
220
+ def __call__(self, question: str) -> str:
221
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
222
+ fixed_answer = self.agent.run(question)
223
+ print(f"Agent returning answer: {fixed_answer}")
224
+ return fixed_answer
225
+
226
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
227
+ """
228
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
229
+ and displays the results.
230
+ """
231
+ # --- Determine HF Space Runtime URL and Repo URL ---
232
+ space_id = "l3xv/Final_Assignment_Template"
233
+
234
+ if profile:
235
+ username= f"{profile.username}"
236
+ print(f"User logged in: {username}")
237
+ else:
238
+ print("User not logged in.")
239
+ return "Please Login to Hugging Face with the button.", None
240
+
241
+ api_url = DEFAULT_API_URL
242
+ questions_url = f"{api_url}/questions"
243
+ submit_url = f"{api_url}/submit"
244
+
245
+ # 1. Instantiate Agent ( modify this part to create your agent)
246
+ try:
247
+ agent = BasicAgent()
248
+ except Exception as e:
249
+ print(f"Error instantiating agent: {e}")
250
+ return f"Error initializing agent: {e}", None
251
+ # 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)
252
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
253
+ print(agent_code)
254
+
255
+ # 2. Fetch Questions
256
+ print(f"Fetching questions from: {questions_url}")
257
+ try:
258
+ response = requests.get(questions_url, timeout=15)
259
+ response.raise_for_status()
260
+ questions_data = response.json()
261
+ if not questions_data:
262
+ print("Fetched questions list is empty.")
263
+ return "Fetched questions list is empty or invalid format.", None
264
+ print(f"Fetched {len(questions_data)} questions.")
265
+ except requests.exceptions.RequestException as e:
266
+ print(f"Error fetching questions: {e}")
267
+ return f"Error fetching questions: {e}", None
268
+ except requests.exceptions.JSONDecodeError as e:
269
+ print(f"Error decoding JSON response from questions endpoint: {e}")
270
+ print(f"Response text: {response.text[:500]}")
271
+ return f"Error decoding server response for questions: {e}", None
272
+ except Exception as e:
273
+ print(f"An unexpected error occurred fetching questions: {e}")
274
+ return f"An unexpected error occurred fetching questions: {e}", None
275
+
276
+ # 3. Run your Agent
277
+ results_log = []
278
+ answers_payload = []
279
+ print(f"Running agent on {len(questions_data)} questions...")
280
+ for item in questions_data:
281
+ task_id = item.get("task_id")
282
+ question_text = item.get("question")
283
+
284
+ # ----------fetch any attached file ----------
285
+ try:
286
+ file_path = download_file_if_any(api_url, task_id)
287
+ except Exception as e:
288
+ file_path = None
289
+ print(f"[file fetch error] {task_id}: {e}")
290
+
291
+ # ---------- Build the prompt sent to the agent ----------
292
+ if file_path:
293
+ q_for_agent = (
294
+ f"{question_text}\n\n"
295
+ f"---\n"
296
+ f"A file was downloaded for this task and saved locally at:\n"
297
+ f"{file_path}\n"
298
+ f"---\n\n"
299
+ )
300
+ else:
301
+ q_for_agent = question_text
302
+
303
+ if not task_id or question_text is None:
304
+ print(f"Skipping item with missing task_id or question: {item}")
305
+ continue
306
+ try:
307
+ submitted_answer = agent(q_for_agent)
308
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
309
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
310
+ except Exception as e:
311
+ print(f"Error running agent on task {task_id}: {e}")
312
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
313
+
314
+ if not answers_payload:
315
+ print("Agent did not produce any answers to submit.")
316
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
317
+
318
+ # 4. Prepare Submission
319
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
320
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
321
+ print(status_update)
322
+
323
+ # 5. Submit
324
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
325
+ try:
326
+ response = requests.post(submit_url, json=submission_data, timeout=60)
327
+ response.raise_for_status()
328
+ result_data = response.json()
329
+ final_status = (
330
+ f"Submission Successful!\n"
331
+ f"User: {result_data.get('username')}\n"
332
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
333
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
334
+ f"Message: {result_data.get('message', 'No message received.')}"
335
+ )
336
+ print("Submission successful.")
337
+ results_df = pd.DataFrame(results_log)
338
+ return final_status, results_df
339
+ except requests.exceptions.HTTPError as e:
340
+ error_detail = f"Server responded with status {e.response.status_code}."
341
+ try:
342
+ error_json = e.response.json()
343
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
344
+ except requests.exceptions.JSONDecodeError:
345
+ error_detail += f" Response: {e.response.text[:500]}"
346
+ status_message = f"Submission Failed: {error_detail}"
347
+ print(status_message)
348
+ results_df = pd.DataFrame(results_log)
349
+ return status_message, results_df
350
+ except requests.exceptions.Timeout:
351
+ status_message = "Submission Failed: The request timed out."
352
+ print(status_message)
353
+ results_df = pd.DataFrame(results_log)
354
+ return status_message, results_df
355
+ except requests.exceptions.RequestException as e:
356
+ status_message = f"Submission Failed: Network error - {e}"
357
+ print(status_message)
358
+ results_df = pd.DataFrame(results_log)
359
+ return status_message, results_df
360
+ except Exception as e:
361
+ status_message = f"An unexpected error occurred during submission: {e}"
362
+ print(status_message)
363
+ results_df = pd.DataFrame(results_log)
364
+ return status_message, results_df
365
+
366
+
367
+ # --- Build Gradio Interface using Blocks ---
368
  with gr.Blocks() as demo:
369
+ gr.Markdown("# Basic Agent Evaluation Runner")
370
+ gr.Markdown(
371
+ """
372
+ **Instructions:**
373
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
374
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
375
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
376
+ ---
377
+ **Disclaimers:**
378
+ 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).
379
+ 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.
380
+ """
381
+ )
382
+
383
+ gr.LoginButton()
384
+
385
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
386
+
387
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
388
+ # Removed max_rows=10 from DataFrame constructor
389
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
390
+
391
+ run_button.click(
392
  fn=run_and_submit_all,
393
+ outputs=[status_output, results_table]
 
394
  )
395
 
396
  if __name__ == "__main__":
397
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
398
+ # Check for SPACE_HOST and SPACE_ID at startup for information
399
+ space_host_startup = os.getenv("SPACE_HOST")
400
+ space_id_startup = "l3xv/Final_Assignment_Template"
401
+
402
+ if space_host_startup:
403
+ print(f"βœ… SPACE_HOST found: {space_host_startup}")
404
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
405
+ else:
406
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
407
+
408
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
409
+ print(f"βœ… SPACE_ID found: {space_id_startup}")
410
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
411
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
412
+ else:
413
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
414
+
415
+ print("-"*(60 + len(" App Starting ")) + "\n")
416
+
417
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
418
+ demo.launch(debug=True, share=False)