bhatanerohan commited on
Commit
015c12b
·
verified ·
1 Parent(s): 5afe89a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +110 -37
app.py CHANGED
@@ -10,61 +10,134 @@ 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
- import os
14
- import time
15
  from functools import lru_cache
 
16
  from openai import OpenAI, RateLimitError, APIError
 
 
17
 
18
- # --- GPT-4o-mini powered agent ------------------------------------------------
19
- class GPT4oMiniAgent:
20
  """
21
- Very small wrapper around OpenAI GPT-4o-mini.
22
- Reads the API key from the OPENAI_API_KEY env variable (set as a HF secret)
23
- • Uses temperature-0 for deterministic grading
24
- Retries on transient rate-limit / 5xx errors with exponential back-off
25
- Caches identical questions in memory so reruns are near-instant
 
 
 
 
 
 
 
 
 
 
26
  """
27
- def __init__(self, max_retries:int=3, backoff:float=2.0):
 
 
 
28
  api_key = os.getenv("OPENAI_API_KEY")
29
  if not api_key:
30
- raise EnvironmentError(
31
- "OPENAI_API_KEY not found – add it in the Space secrets!"
32
- )
33
  self.client = OpenAI(api_key=api_key)
34
- self.max_retries = max_retries
35
- self.backoff = backoff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  self.system_prompt = (
37
- "You are a concise, precise assistant. "
38
- "Answer fact questions directly. "
39
- "When asked for code, return valid Python in fenced ``` blocks."
 
 
40
  )
41
- print("✅ GPT4oMiniAgent initialised.")
42
 
43
- # in-memory cache so if the grader calls the same Q twice we don’t spend tokens
44
- @lru_cache(maxsize=256)
45
- def __call__(self, question: str) -> str: # noqa: D401
46
- print(f"🔹 Asking gpt-4o-mini: {question[:60]}…")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  for attempt in range(1, self.max_retries + 1):
48
  try:
49
- response = self.client.chat.completions.create(
50
- model="gpt-4o-mini", # ← official name :contentReference[oaicite:0]{index=0}
51
- messages=[
52
- {"role": "system", "content": self.system_prompt},
53
- {"role": "user", "content": question},
54
- ],
55
- max_tokens=512,
56
  temperature=0.0,
 
 
57
  )
58
- answer = response.choices[0].message.content.strip()
59
- print(f"🔸 Answer (truncated): {answer[:60]}…")
60
- return answer
61
  except (RateLimitError, APIError) as e:
62
  wait = self.backoff * attempt
63
- print(f"⚠️ OpenAI error: {e}. Retry {attempt}/{self.max_retries} after {wait}s.")
64
  time.sleep(wait)
65
- # If all retries failed
66
- return "I’m sorry, I could not generate an answer due to repeated API errors."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
 
 
 
68
 
69
  def run_and_submit_all( profile: gr.OAuthProfile | None):
70
  """
@@ -87,7 +160,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
87
 
88
  # 1. Instantiate Agent ( modify this part to create your agent)
89
  try:
90
- agent = BasicAgent()
91
  except Exception as e:
92
  print(f"Error instantiating agent: {e}")
93
  return f"Error initializing agent: {e}", None
 
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 language; include citations if you used the web."
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
  """
 
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