bhatanerohan commited on
Commit
1e620c1
Β·
verified Β·
1 Parent(s): 8323d3a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -74
app.py CHANGED
@@ -1,6 +1,5 @@
1
- # app.py – handles images, txt/py, PDFs, any file…
2
-
3
- import os, json, time, io, mimetypes
4
  from functools import lru_cache
5
 
6
  import gradio as gr
@@ -8,20 +7,20 @@ import requests
8
  import pandas as pd
9
  from openai import OpenAI, RateLimitError, APIError
10
  from duckduckgo_search import DDGS
11
- from PyPDF2 import PdfReader # <- new dependency
12
 
13
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
14
  OPENAI_MODEL = "gpt-4o-mini"
15
- TEXT_CHAR_LIMIT = 8_000
16
- PDF_PAGE_LIMIT = 3
 
17
 
18
- # ───────────────────────── helpers ─────────────────────────
19
  def duckduckgo_search(query: str, max_results: int = 5) -> str:
20
- bullets = []
21
  with DDGS() as ddgs:
22
- for r in ddgs.text(query, max_results=max_results):
23
- bullets.append(f"- {r['title']} – {r['href']}")
24
- return "\n".join(bullets) or "No results."
25
 
26
  DDG_SCHEMA = {
27
  "name": "duckduckgo_search",
@@ -36,57 +35,81 @@ DDG_SCHEMA = {
36
  },
37
  }
38
 
39
- def fetch_text_file(url: str) -> str:
 
 
 
 
 
 
 
 
40
  try:
41
- txt = requests.get(url, timeout=15).text
42
- return txt[:TEXT_CHAR_LIMIT]
43
  except Exception as e:
44
- return f"[Could not download text file: {e}]"
45
 
46
- def fetch_pdf_text(url: str) -> str:
47
  try:
48
- resp = requests.get(url, timeout=20)
49
- resp.raise_for_status()
50
- reader = PdfReader(io.BytesIO(resp.content))
51
- pages = []
52
- for i, page in enumerate(reader.pages[:PDF_PAGE_LIMIT]):
53
- pages.append(page.extract_text() or "")
54
- return ("\n\n".join(pages))[:TEXT_CHAR_LIMIT]
55
  except Exception as e:
56
  return f"[Could not read PDF: {e}]"
57
 
58
- # ─────────────────────────── agent ─────────────────────────
59
- class GPT4oMiniAgentWithFiles:
60
- def __init__(self, retries:int = 3, backoff:float = 2.0):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  key = os.getenv("OPENAI_API_KEY")
62
  if not key:
63
- raise EnvironmentError("OPENAI_API_KEY missing in Secrets.")
64
  self.client, self.retries, self.backoff = OpenAI(api_key=key), retries, backoff
65
- self.sys_prompt = (
66
- "You are a concise, accurate assistant. "
67
- "If certain, answer directly; otherwise call duckduckgo_search."
68
  )
69
 
70
  @lru_cache(maxsize=512)
71
- def __call__(self, question:str, file_url:str|None=None) -> str:
72
- user_content = [{"type": "text", "text": question}]
 
73
  if file_url:
74
- kind = (file_url.split("?")[0].split("#")[0]).lower()
75
- ext = os.path.splitext(kind)[1]
76
  if ext in {".png", ".jpg", ".jpeg", ".gif", ".webp"}:
77
- user_content.append({"type":"image_url","image_url":{"url":file_url}})
78
- elif ext == ".pdf":
79
- text = fetch_pdf_text(file_url)
80
- user_content.append({"type":"text","text": f"(PDF extract)\n{text}"})
 
81
  elif ext in {".txt", ".py", ".md", ".json", ".csv", ".html"}:
82
- text = fetch_text_file(file_url)
83
- user_content.append({"type":"text","text": f"(File content)\n{text}"})
 
84
  else:
85
- user_content.append({"type":"text","text": f"[File available here] {file_url}"})
86
 
87
  msgs = [
88
- {"role":"system","content":self.sys_prompt},
89
- {"role":"user","content":user_content},
90
  ]
91
 
92
  resp = self._chat(msgs, tools=[DDG_SCHEMA], tool_choice="auto")
@@ -94,64 +117,56 @@ class GPT4oMiniAgentWithFiles:
94
  if resp.choices[0].message.tool_calls:
95
  for call in resp.choices[0].message.tool_calls:
96
  args = json.loads(call.function.arguments or "{}")
97
- tool_out = duckduckgo_search(**args)
98
- msgs.append({
99
- "role":"tool",
100
- "tool_call_id":call.id,
101
- "name":call.function.name,
102
- "content":tool_out,
103
- })
104
  resp = self._chat(msgs)
105
 
106
  return resp.choices[0].message.content.strip()
107
 
108
  def _chat(self, messages, **kw):
109
- for i in range(1, self.retries+1):
110
  try:
111
  return self.client.chat.completions.create(
112
- model=OPENAI_MODEL,
113
- messages=messages,
114
- temperature=0.0,
115
- max_tokens=512,
116
- **kw
117
  )
118
  except (RateLimitError, APIError):
119
  time.sleep(self.backoff * i)
120
  raise RuntimeError("OpenAI API failed after retries.")
121
 
122
- # ────────────────── run + submit ──────────────────
123
- def run_and_submit_all(profile: gr.OAuthProfile|None):
124
  if not profile:
125
  return "Please log in ↑", None
126
  username = profile.username
127
- agent = GPT4oMiniAgentWithFiles()
128
- space_id = os.getenv("SPACE_ID","local")
129
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
130
 
131
- qs = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15).json()
132
 
133
  rows, answers = [], []
134
- for item in qs:
135
- qid = item["task_id"]
136
- text = item["question"]
137
- file_url = item.get("filename") or item.get("file_url")
138
- ans = agent(text, file_url)
139
  answers.append({"task_id": qid, "submitted_answer": ans})
140
- rows.append({"Task ID": qid, "Question": text, "File": file_url or "", "Answer": ans})
141
 
142
  payload = {"username": username, "agent_code": agent_code, "answers": answers}
143
  res = requests.post(f"{DEFAULT_API_URL}/submit", json=payload, timeout=60).json()
144
- status = f"Score {res['score']} % ({res['correct_count']}/{res['total_attempted']})"
145
  return status, pd.DataFrame(rows)
146
 
147
- # ────────────────────── UI ───────────────────────
148
  with gr.Blocks() as demo:
149
- gr.Markdown("# Unit-4 Agent – handles images, text/code files & PDFs")
150
  gr.LoginButton()
151
- btn = gr.Button("Run Evaluation & Submit All Answers")
152
- status = gr.Textbox(label="Status", interactive=False)
153
- table = gr.DataFrame(label="Log", wrap=True)
154
- btn.click(run_and_submit_all, outputs=[status, table])
155
 
156
  if __name__ == "__main__":
157
  demo.launch(debug=True, share=False)
 
1
+ # app.py β€” handles images, PDFs, text/code, Excel, audio, etc.
2
+ import os, json, time, io, tempfile, mimetypes
 
3
  from functools import lru_cache
4
 
5
  import gradio as gr
 
7
  import pandas as pd
8
  from openai import OpenAI, RateLimitError, APIError
9
  from duckduckgo_search import DDGS
10
+ from PyPDF2 import PdfReader
11
 
12
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
  OPENAI_MODEL = "gpt-4o-mini"
14
+ TEXT_LIMIT = 8_000
15
+ PDF_PAGES = 3
16
+ AUDIO_SIZE_CAP = 16 * 1024 * 1024 # 16 MB
17
 
18
+ # ─────────────── helpers ───────────────
19
  def duckduckgo_search(query: str, max_results: int = 5) -> str:
 
20
  with DDGS() as ddgs:
21
+ hits = [f"- {r['title']} – {r['href']}"
22
+ for r in ddgs.text(query, max_results=max_results)]
23
+ return "\n".join(hits) or "No results found."
24
 
25
  DDG_SCHEMA = {
26
  "name": "duckduckgo_search",
 
35
  },
36
  }
37
 
38
+ def download_bytes(url: str, cap: int | None = None) -> bytes:
39
+ r = requests.get(url, timeout=20)
40
+ r.raise_for_status()
41
+ data = r.content
42
+ if cap and len(data) > cap:
43
+ raise ValueError("File too large")
44
+ return data
45
+
46
+ def extract_text_file(url: str) -> str:
47
  try:
48
+ txt = download_bytes(url).decode(errors="replace")
49
+ return txt[:TEXT_LIMIT]
50
  except Exception as e:
51
+ return f"[Could not fetch text file: {e}]"
52
 
53
+ def extract_pdf(url: str) -> str:
54
  try:
55
+ reader = PdfReader(io.BytesIO(download_bytes(url)))
56
+ pages = [reader.pages[i].extract_text() or "" for i in range(min(PDF_PAGES, len(reader.pages)))]
57
+ return ("\n\n".join(pages))[:TEXT_LIMIT]
 
 
 
 
58
  except Exception as e:
59
  return f"[Could not read PDF: {e}]"
60
 
61
+ def extract_excel(url: str) -> str:
62
+ try:
63
+ buf = io.BytesIO(download_bytes(url))
64
+ df = pd.read_excel(buf, nrows=15, engine="openpyxl")
65
+ return df.to_csv(index=False, header=True)[:TEXT_LIMIT]
66
+ except Exception as e:
67
+ return f"[Could not read Excel: {e}]"
68
+
69
+ def transcribe_audio(url: str, client: OpenAI) -> str:
70
+ try:
71
+ data = download_bytes(url, cap=AUDIO_SIZE_CAP)
72
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".audio") as tmp:
73
+ tmp.write(data); tmp.flush()
74
+ tr = client.audio.transcriptions.create(model="whisper-1", file=open(tmp.name, "rb"))
75
+ return tr.text[:2000]
76
+ except Exception as e:
77
+ return f"[Could not transcribe audio: {e}]"
78
+
79
+ # ─────────────── Agent ───────────────
80
+ class GPT4oMiniAgent:
81
+ def __init__(self, retries=3, backoff=2.0):
82
  key = os.getenv("OPENAI_API_KEY")
83
  if not key:
84
+ raise EnvironmentError("Add OPENAI_API_KEY in Space Secrets")
85
  self.client, self.retries, self.backoff = OpenAI(api_key=key), retries, backoff
86
+ self.system_prompt = (
87
+ "You are a concise, accurate assistant. If certain, answer directly; "
88
+ "if not, call duckduckgo_search first."
89
  )
90
 
91
  @lru_cache(maxsize=512)
92
+ def __call__(self, question: str, file_url: str | None = None) -> str:
93
+ user_parts = [{"type": "text", "text": question}]
94
+
95
  if file_url:
96
+ ext = os.path.splitext(file_url.split("?")[0].split("#")[0])[1].lower()
 
97
  if ext in {".png", ".jpg", ".jpeg", ".gif", ".webp"}:
98
+ user_parts.append({"type": "image_url", "image_url": {"url": file_url}})
99
+ elif ext in {".pdf"}:
100
+ user_parts.append({"type": "text", "text": "(PDF extract)\n" + extract_pdf(file_url)})
101
+ elif ext in {".xls", ".xlsx"}:
102
+ user_parts.append({"type": "text", "text": "(Excel preview)\n" + extract_excel(file_url)})
103
  elif ext in {".txt", ".py", ".md", ".json", ".csv", ".html"}:
104
+ user_parts.append({"type": "text", "text": "(File content)\n" + extract_text_file(file_url)})
105
+ elif ext in {".mp3", ".wav", ".m4a", ".flac", ".ogg"}:
106
+ user_parts.append({"type": "text", "text": "(Audio transcript)\n" + transcribe_audio(file_url, self.client)})
107
  else:
108
+ user_parts.append({"type": "text", "text": f"[File available: {file_url}]"} )
109
 
110
  msgs = [
111
+ {"role": "system", "content": self.system_prompt},
112
+ {"role": "user", "content": user_parts},
113
  ]
114
 
115
  resp = self._chat(msgs, tools=[DDG_SCHEMA], tool_choice="auto")
 
117
  if resp.choices[0].message.tool_calls:
118
  for call in resp.choices[0].message.tool_calls:
119
  args = json.loads(call.function.arguments or "{}")
120
+ search_out = duckduckgo_search(**args)
121
+ msgs.append({"role": "tool", "tool_call_id": call.id, "name": call.function.name, "content": search_out})
 
 
 
 
 
122
  resp = self._chat(msgs)
123
 
124
  return resp.choices[0].message.content.strip()
125
 
126
  def _chat(self, messages, **kw):
127
+ for i in range(1, self.retries + 1):
128
  try:
129
  return self.client.chat.completions.create(
130
+ model=OPENAI_MODEL, messages=messages,
131
+ temperature=0.0, max_tokens=512, **kw
 
 
 
132
  )
133
  except (RateLimitError, APIError):
134
  time.sleep(self.backoff * i)
135
  raise RuntimeError("OpenAI API failed after retries.")
136
 
137
+ # ─────────────── pipeline ───────────────
138
+ def run_and_submit_all(profile: gr.OAuthProfile | None):
139
  if not profile:
140
  return "Please log in ↑", None
141
  username = profile.username
142
+ agent = GPT4oMiniAgent()
143
+ space_id = os.getenv("SPACE_ID", "local")
144
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
145
 
146
+ questions = requests.get(f"{DEFAULT_API_URL}/questions", timeout=15).json()
147
 
148
  rows, answers = [], []
149
+ for q in questions:
150
+ qid = q["task_id"]
151
+ qtext = q["question"]
152
+ fileu = q.get("filename") or q.get("file_url")
153
+ ans = agent(qtext, fileu)
154
  answers.append({"task_id": qid, "submitted_answer": ans})
155
+ rows.append({"Task ID": qid, "Question": qtext, "File": fileu or "", "Answer": ans})
156
 
157
  payload = {"username": username, "agent_code": agent_code, "answers": answers}
158
  res = requests.post(f"{DEFAULT_API_URL}/submit", json=payload, timeout=60).json()
159
+ status = f"Score {res['score']} % ({res['correct_count']}/{res['total_attempted']})"
160
  return status, pd.DataFrame(rows)
161
 
162
+ # ─────────────── UI ───────────────
163
  with gr.Blocks() as demo:
164
+ gr.Markdown("# Unit-4 Agent – images, PDFs, Excel, audio, text, etc.")
165
  gr.LoginButton()
166
+ run = gr.Button("Run Evaluation & Submit All Answers")
167
+ out_status = gr.Textbox(label="Status", interactive=False)
168
+ out_table = gr.DataFrame(label="Log", wrap=True)
169
+ run.click(run_and_submit_all, outputs=[out_status, out_table])
170
 
171
  if __name__ == "__main__":
172
  demo.launch(debug=True, share=False)