bhatanerohan commited on
Commit
957d1ee
ยท
verified ยท
1 Parent(s): c69e9f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -280
app.py CHANGED
@@ -1,316 +1,143 @@
1
- import os
2
- 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
-
11
- # --- Basic Agent Definition ---
12
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
13
- # ----------------------------------------------------------
14
  import os, json, time
15
  from functools import lru_cache
16
 
 
 
 
17
  from openai import OpenAI, RateLimitError, APIError
18
- from duckduckgo_search import DDGS # pip install duckduckgo-search
19
 
20
-
21
- # ---------- simple search helper -------------------------------------------
 
 
22
  def duckduckgo_search(query: str, max_results: int = 5) -> str:
23
- """
24
- Returns a plain-text bulleted list of the first `max_results`
25
- DuckDuckGo results: Title โ€“ URL
26
- """
27
- print(f"๐Ÿ” DuckDuckGo search: {query!r}")
28
  bullets = []
29
  with DDGS() as ddgs:
30
  for r in ddgs.text(query, max_results=max_results):
31
  bullets.append(f"- {r['title']} โ€“ {r['href']}")
32
- if not bullets:
33
- return "No results."
34
- return "\n".join(bullets[:max_results])
35
-
36
-
37
- # ---------- agent ----------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
38
  class GPT4oMiniAgentWithDDG:
39
- """
40
- GPT-4o-mini with an optional DuckDuckGo search tool.
41
- The model decides โ€“ via function-calling โ€“ whether it needs live info.
42
- """
43
- def __init__(self, max_retries: int = 3, backoff: float = 2.0):
44
  api_key = os.getenv("OPENAI_API_KEY")
45
  if not api_key:
46
- raise EnvironmentError("OPENAI_API_KEY secret not set!")
47
-
48
- self.client = OpenAI(api_key=api_key)
49
- self.max_retries, self.backoff = max_retries, backoff
50
-
51
- # JSON schema that we register as a tool
52
- self.ddg_schema = {
53
- "name": "duckduckgo_search",
54
- "description": "Search the web for up-to-date information when knowledge cutoff may be too old.",
55
- "parameters": {
56
- "type": "object",
57
- "properties": {
58
- "query": {"type": "string", "description": "search string"},
59
- "max_results": {
60
- "type": "integer",
61
- "description": "how many results to return (1-10)",
62
- "default": 5,
63
- },
64
- },
65
- "required": ["query"],
66
- },
67
- }
68
-
69
- self.system_prompt = (
70
- "You are a concise, accurate assistant.\n"
71
- "If you are **certain** you already know the answer, answer directly.\n"
72
- "If the question is about very recent events or you are unsure, call "
73
- "`duckduckgo_search` to look it up first.\n"
74
- "Return final answers in plain consice language one word or only the actual answer "
75
  )
76
 
77
- print("โœ… GPT4oMiniAgentWithDDG ready.")
78
-
79
- # ----------------------------------------------------------------------
80
- # in-memory cache so repeats donโ€™t cost tokens or web calls
81
  @lru_cache(maxsize=512)
82
- def __call__(self, question: str) -> str:
83
- msg_log = [
84
- {"role": "system", "content": self.system_prompt},
85
- {"role": "user", "content": question},
 
 
 
 
 
 
86
  ]
87
 
88
- # 1st request: let model decide if it needs the search tool
89
- first = self._chat(msg_log, tools=[self.ddg_schema], tool_choice="auto")
90
 
91
- # If the model called the tool, run it and feed the results back
92
- if first.choices[0].message.tool_calls:
93
- answer = self._handle_tool_calls(msg_log, first)
94
- else:
95
- answer = first.choices[0].message.content.strip()
 
 
 
 
 
 
 
96
 
97
- print(f"๐Ÿ”ธ Final answer (trunc.): {answer[:70]}โ€ฆ")
98
- return answer
99
 
100
- # ---------- helpers ----------------------------------------------------
101
- def _chat(self, messages, **kwargs):
102
- """wrapper with retries"""
103
- for attempt in range(1, self.max_retries + 1):
104
  try:
105
  return self.client.chat.completions.create(
106
- model="gpt-4o-mini",
107
  messages=messages,
108
  temperature=0.0,
109
  max_tokens=512,
110
- **kwargs,
111
  )
112
- except (RateLimitError, APIError) as e:
113
- wait = self.backoff * attempt
114
- print(f"โš ๏ธ OpenAI error {e}. Retry {attempt}/{self.max_retries} in {wait}s.")
115
- time.sleep(wait)
116
  raise RuntimeError("OpenAI API failed after retries.")
117
 
118
- def _handle_tool_calls(self, msg_log, first_response):
119
- """Execute each requested tool then get the modelโ€™s final answer."""
120
- for call in first_response.choices[0].message.tool_calls:
121
- name = call.function.name
122
- args = json.loads(call.function.arguments or "{}")
123
- if name == "duckduckgo_search":
124
- content = duckduckgo_search(**args)
125
- else:
126
- content = f"Tool {name} not implemented."
127
-
128
- # Append the tool result so the model can read it
129
- msg_log.append(
130
- {
131
- "role": "tool",
132
- "tool_call_id": call.id,
133
- "name": name,
134
- "content": content,
135
- }
136
- )
137
-
138
- # Ask the model again, now that it has the web data
139
- final = self._chat(msg_log)
140
- return final.choices[0].message.content.strip()
141
-
142
- def run_and_submit_all( profile: gr.OAuthProfile | None):
143
- """
144
- Fetches all questions, runs the BasicAgent on them, submits all answers,
145
- and displays the results.
146
- """
147
- # --- Determine HF Space Runtime URL and Repo URL ---
148
- space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
149
-
150
- if profile:
151
- username= f"{profile.username}"
152
- print(f"User logged in: {username}")
153
- else:
154
- print("User not logged in.")
155
- return "Please Login to Hugging Face with the button.", None
156
-
157
- api_url = DEFAULT_API_URL
158
- questions_url = f"{api_url}/questions"
159
- submit_url = f"{api_url}/submit"
160
-
161
- # 1. Instantiate Agent ( modify this part to create your agent)
162
- try:
163
- agent = GPT4oMiniAgentWithDDG()
164
- except Exception as e:
165
- print(f"Error instantiating agent: {e}")
166
- return f"Error initializing agent: {e}", None
167
- # 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)
168
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
169
- print(agent_code)
170
-
171
- # 2. Fetch Questions
172
- print(f"Fetching questions from: {questions_url}")
173
- try:
174
- response = requests.get(questions_url, timeout=15)
175
- response.raise_for_status()
176
- questions_data = response.json()
177
- if not questions_data:
178
- print("Fetched questions list is empty.")
179
- return "Fetched questions list is empty or invalid format.", None
180
- print(f"Fetched {len(questions_data)} questions.")
181
- except requests.exceptions.RequestException as e:
182
- print(f"Error fetching questions: {e}")
183
- return f"Error fetching questions: {e}", None
184
- except requests.exceptions.JSONDecodeError as e:
185
- print(f"Error decoding JSON response from questions endpoint: {e}")
186
- print(f"Response text: {response.text[:500]}")
187
- return f"Error decoding server response for questions: {e}", None
188
- except Exception as e:
189
- print(f"An unexpected error occurred fetching questions: {e}")
190
- return f"An unexpected error occurred fetching questions: {e}", None
191
-
192
- # 3. Run your Agent
193
- results_log = []
194
- answers_payload = []
195
- print(f"Running agent on {len(questions_data)} questions...")
196
- for item in questions_data:
197
- task_id = item.get("task_id")
198
- question_text = item.get("question")
199
- if not task_id or question_text is None:
200
- print(f"Skipping item with missing task_id or question: {item}")
201
- continue
202
- try:
203
- submitted_answer = agent(question_text)
204
- answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
205
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
206
- except Exception as e:
207
- print(f"Error running agent on task {task_id}: {e}")
208
- results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
209
 
210
- if not answers_payload:
211
- print("Agent did not produce any answers to submit.")
212
- return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
213
-
214
- # 4. Prepare Submission
215
- submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
216
- status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
217
- print(status_update)
218
-
219
- # 5. Submit
220
- print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
221
- try:
222
- response = requests.post(submit_url, json=submission_data, timeout=60)
223
- response.raise_for_status()
224
- result_data = response.json()
225
- final_status = (
226
- f"Submission Successful!\n"
227
- f"User: {result_data.get('username')}\n"
228
- f"Overall Score: {result_data.get('score', 'N/A')}% "
229
- f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
230
- f"Message: {result_data.get('message', 'No message received.')}"
231
- )
232
- print("Submission successful.")
233
- results_df = pd.DataFrame(results_log)
234
- return final_status, results_df
235
- except requests.exceptions.HTTPError as e:
236
- error_detail = f"Server responded with status {e.response.status_code}."
237
- try:
238
- error_json = e.response.json()
239
- error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
240
- except requests.exceptions.JSONDecodeError:
241
- error_detail += f" Response: {e.response.text[:500]}"
242
- status_message = f"Submission Failed: {error_detail}"
243
- print(status_message)
244
- results_df = pd.DataFrame(results_log)
245
- return status_message, results_df
246
- except requests.exceptions.Timeout:
247
- status_message = "Submission Failed: The request timed out."
248
- print(status_message)
249
- results_df = pd.DataFrame(results_log)
250
- return status_message, results_df
251
- except requests.exceptions.RequestException as e:
252
- status_message = f"Submission Failed: Network error - {e}"
253
- print(status_message)
254
- results_df = pd.DataFrame(results_log)
255
- return status_message, results_df
256
- except Exception as e:
257
- status_message = f"An unexpected error occurred during submission: {e}"
258
- print(status_message)
259
- results_df = pd.DataFrame(results_log)
260
- return status_message, results_df
261
-
262
-
263
- # --- Build Gradio Interface using Blocks ---
264
  with gr.Blocks() as demo:
265
- gr.Markdown("# Basic Agent Evaluation Runner")
266
- gr.Markdown(
267
- """
268
- **Instructions:**
269
-
270
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
271
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
272
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
273
-
274
- ---
275
- **Disclaimers:**
276
- 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).
277
- 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.
278
- """
279
- )
280
-
281
  gr.LoginButton()
 
 
 
282
 
283
- run_button = gr.Button("Run Evaluation & Submit All Answers")
284
-
285
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
286
- # Removed max_rows=10 from DataFrame constructor
287
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
288
-
289
- run_button.click(
290
- fn=run_and_submit_all,
291
- outputs=[status_output, results_table]
292
- )
293
 
294
  if __name__ == "__main__":
295
- print("\n" + "-"*30 + " App Starting " + "-"*30)
296
- # Check for SPACE_HOST and SPACE_ID at startup for information
297
- space_host_startup = os.getenv("SPACE_HOST")
298
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
299
-
300
- if space_host_startup:
301
- print(f"โœ… SPACE_HOST found: {space_host_startup}")
302
- print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
303
- else:
304
- print("โ„น๏ธ SPACE_HOST environment variable not found (running locally?).")
305
-
306
- if space_id_startup: # Print repo URLs if SPACE_ID is found
307
- print(f"โœ… SPACE_ID found: {space_id_startup}")
308
- print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
309
- print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
310
- else:
311
- print("โ„น๏ธ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
312
-
313
- print("-"*(60 + len(" App Starting ")) + "\n")
314
-
315
- print("Launching Gradio Interface for Basic Agent Evaluation...")
316
- demo.launch(debug=True, share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os, json, time
2
  from functools import lru_cache
3
 
4
+ import gradio as gr
5
+ import requests
6
+ import pandas as pd
7
  from openai import OpenAI, RateLimitError, APIError
8
+ from duckduckgo_search import DDGS # add to requirements.txt
9
 
10
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
11
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
12
+ OPENAI_MODEL = "gpt-4o-mini"
13
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
  def duckduckgo_search(query: str, max_results: int = 5) -> str:
 
 
 
 
 
15
  bullets = []
16
  with DDGS() as ddgs:
17
  for r in ddgs.text(query, max_results=max_results):
18
  bullets.append(f"- {r['title']} โ€“ {r['href']}")
19
+ return "\n".join(bullets) or "No results."
20
+
21
+ DDG_SCHEMA = {
22
+ "name": "duckduckgo_search",
23
+ "description": "Search the web for up-to-date info.",
24
+ "parameters": {
25
+ "type": "object",
26
+ "properties": {
27
+ "query": {"type": "string"},
28
+ "max_results": {"type": "integer", "default": 5},
29
+ },
30
+ "required": ["query"],
31
+ },
32
+ }
33
+
34
+ # โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
35
+ # โ”‚ AGENT (now supports optional image_url) โ”‚
36
+ # โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
37
  class GPT4oMiniAgentWithDDG:
38
+ def __init__(self, retries:int = 3, backoff:float = 2.0):
 
 
 
 
39
  api_key = os.getenv("OPENAI_API_KEY")
40
  if not api_key:
41
+ raise EnvironmentError("Add OPENAI_API_KEY in your Space secrets!")
42
+ self.client = OpenAI(api_key=api_key)
43
+ self.retries = retries
44
+ self.backoff = backoff
45
+ self.prompt = (
46
+ "You are a concise, accurate assistant. "
47
+ "If certain, answer immediately; otherwise call duckduckgo_search."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  )
49
 
 
 
 
 
50
  @lru_cache(maxsize=512)
51
+ def __call__(self, question: str, image_url: str | None = None) -> str:
52
+ user_content = [{"type": "text", "text": question}]
53
+ if image_url:
54
+ user_content.append(
55
+ {"type": "image_url", "image_url": {"url": image_url}}
56
+ )
57
+
58
+ msgs = [
59
+ {"role": "system", "content": self.prompt},
60
+ {"role": "user", "content": user_content},
61
  ]
62
 
63
+ # 1st pass โ€“ model may request the tool
64
+ resp = self._chat(msgs, tools=[DDG_SCHEMA], tool_choice="auto")
65
 
66
+ # Run tool(s) if requested
67
+ if resp.choices[0].message.tool_calls:
68
+ for call in resp.choices[0].message.tool_calls:
69
+ args = json.loads(call.function.arguments or "{}")
70
+ tool_out = duckduckgo_search(**args) if call.function.name=="duckduckgo_search" else ""
71
+ msgs.append({
72
+ "role": "tool",
73
+ "tool_call_id": call.id,
74
+ "name": call.function.name,
75
+ "content": tool_out
76
+ })
77
+ resp = self._chat(msgs)
78
 
79
+ return resp.choices[0].message.content.strip()
 
80
 
81
+ def _chat(self, messages, **kw):
82
+ for i in range(1, self.retries + 1):
 
 
83
  try:
84
  return self.client.chat.completions.create(
85
+ model=OPENAI_MODEL,
86
  messages=messages,
87
  temperature=0.0,
88
  max_tokens=512,
89
+ **kw
90
  )
91
+ except (RateLimitError, APIError):
92
+ time.sleep(self.backoff * i)
 
 
93
  raise RuntimeError("OpenAI API failed after retries.")
94
 
95
+ # โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
96
+ # โ”‚ RUN + SUBMIT โ”‚
97
+ # โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
98
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
99
+ if not profile:
100
+ return "Please log in โ†‘", None
101
+ username = profile.username
102
+ agent = GPT4oMiniAgentWithDDG()
103
+ space_id = os.getenv("SPACE_ID", "local")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ # โ‘  Fetch
107
+ qs = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15).json()
108
+
109
+ # โ‘ก Answer
110
+ answers, rows = [], []
111
+ for item in qs:
112
+ qid = item["task_id"]
113
+ text = item["question"]
114
+ img = item.get("filename") # <-- NEW
115
+ ans = agent(text, img)
116
+ answers.append({"task_id": qid, "submitted_answer": ans})
117
+ rows.append({"Task ID": qid, "Question": text, "Image URL": img or "", "Answer": ans})
118
+
119
+ # โ‘ข Submit
120
+ payload = {
121
+ "username": username,
122
+ "agent_code": agent_code,
123
+ "answers": answers
124
+ }
125
+ res = requests.post(f"{DEFAULT_API_URL}/submit", json=payload, timeout=60).json()
126
+ status = f"Score {res['score']} % ({res['correct_count']}/{res['total_attempted']})"
127
+
128
+ return status, pd.DataFrame(rows)
129
+
130
+ # โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
131
+ # โ”‚ GRADIO UI โ”‚
132
+ # โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  with gr.Blocks() as demo:
134
+ gr.Markdown("# Unit-4 Agent Runner โ€“ Image Ready")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  gr.LoginButton()
136
+ run_btn = gr.Button("Run Evaluation & Submit All Answers")
137
+ status_box = gr.Textbox(label="Status", interactive=False)
138
+ results_grid = gr.DataFrame(label="Log", wrap=True)
139
 
140
+ run_btn.click(run_and_submit_all, outputs=[status_box, results_grid])
 
 
 
 
 
 
 
 
 
141
 
142
  if __name__ == "__main__":
143
+ demo.launch(debug=True, share=False)