Files changed (1) hide show
  1. app.py +181 -13
app.py CHANGED
@@ -1,23 +1,193 @@
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
- class BasicAgent:
14
- def __init__(self):
15
- print("BasicAgent initialized.")
16
  def __call__(self, question: str) -> str:
17
- print(f"Agent received question (first 50 chars): {question[:50]}...")
18
- fixed_answer = "This is a default answer."
19
- print(f"Agent returning fixed answer: {fixed_answer}")
20
- return fixed_answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  def run_and_submit_all( profile: gr.OAuthProfile | None):
23
  """
@@ -40,7 +210,7 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
40
 
41
  # 1. Instantiate Agent ( modify this part to create your agent)
42
  try:
43
- agent = BasicAgent()
44
  except Exception as e:
45
  print(f"Error instantiating agent: {e}")
46
  return f"Error initializing agent: {e}", None
@@ -146,11 +316,9 @@ with gr.Blocks() as demo:
146
  gr.Markdown(
147
  """
148
  **Instructions:**
149
-
150
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
151
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
152
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
153
-
154
  ---
155
  **Disclaimers:**
156
  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).
 
1
  import os
2
+ import re
3
+ import json
4
  import gradio as gr
5
  import requests
 
6
  import pandas as pd
7
+ from urllib.parse import quote
8
+ from bs4 import BeautifulSoup
9
+ from dotenv import load_dotenv
10
+
11
+ load_dotenv()
12
 
13
  # (Keep Constants as is)
14
  # --- Constants ---
15
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
16
+ HF_TOKEN = os.getenv("HF_TOKEN", "")
17
+ REACT_MAX_STEPS = 10
18
+ LLM_MODEL = "Qwen/Qwen2.5-7B-Instruct"
19
+
20
+
21
+ # --- Tools (DuckDuckGo search, web page view, code agent) ---
22
+ def tool_web_search(query: str, max_results: int = 5) -> str:
23
+ """Search the web using DuckDuckGo. Input: search query string."""
24
+ try:
25
+ from duckduckgo_search import DDGS
26
+ results = list(DDGS().text(query, max_results=max_results))
27
+ if not results:
28
+ return "No search results found."
29
+ out = []
30
+ for i, r in enumerate(results, 1):
31
+ out.append(f"{i}. {r.get('title', '')}\n URL: {r.get('href', '')}\n {r.get('body', '')}")
32
+ return "\n\n".join(out)
33
+ except Exception as e:
34
+ return f"Web search error: {e}"
35
+
36
+
37
+ def tool_web_page_view(url: str) -> str:
38
+ """View the main text content of a web page. Input: full URL string."""
39
+ try:
40
+ headers = {"User-Agent": "Mozilla/5.0 (compatible; ReActAgent/1.0)"}
41
+ r = requests.get(url, timeout=15, headers=headers)
42
+ r.raise_for_status()
43
+ soup = BeautifulSoup(r.text, "html.parser")
44
+ for tag in soup(["script", "style", "nav", "footer", "header"]):
45
+ tag.decompose()
46
+ text = soup.get_text(separator="\n", strip=True)
47
+ return text[:8000] if len(text) > 8000 else text or "No text content found."
48
+ except Exception as e:
49
+ return f"Web page view error: {e}"
50
+
51
+
52
+ def tool_code_agent(code: str) -> str:
53
+ """Run Python code to compute an answer. Input: a single Python expression or block (e.g. print(2+2)). No file or network access."""
54
+ import builtins
55
+ import io
56
+ import sys
57
+ safe_builtins = {
58
+ "abs": builtins.abs, "all": builtins.all, "any": builtins.any,
59
+ "bin": builtins.bin, "bool": builtins.bool, "chr": builtins.chr,
60
+ "dict": builtins.dict, "divmod": builtins.divmod, "enumerate": builtins.enumerate,
61
+ "filter": builtins.filter, "float": builtins.float, "format": builtins.format,
62
+ "hash": builtins.hash, "int": builtins.int, "len": builtins.len,
63
+ "list": builtins.list, "map": builtins.map, "max": builtins.max,
64
+ "min": builtins.min, "next": builtins.next, "pow": builtins.pow,
65
+ "print": builtins.print, "range": builtins.range, "repr": builtins.repr,
66
+ "reversed": builtins.reversed, "round": builtins.round, "set": builtins.set,
67
+ "sorted": builtins.sorted, "str": builtins.str, "sum": builtins.sum,
68
+ "tuple": builtins.tuple, "zip": builtins.zip,
69
+ }
70
+ try:
71
+ code = code.strip()
72
+ if not code.startswith("print(") and "print(" not in code:
73
+ code = f"print({code})"
74
+ buf = io.StringIO()
75
+ old_stdout = sys.stdout
76
+ sys.stdout = buf
77
+ try:
78
+ exec(code, {"__builtins__": safe_builtins, "print": builtins.print}, {})
79
+ finally:
80
+ sys.stdout = old_stdout
81
+ return buf.getvalue().strip() or "Code ran (no printed output)."
82
+ except Exception as e:
83
+ return f"Code error: {e}"
84
+
85
+
86
+ TOOLS = {
87
+ "web_search": tool_web_search,
88
+ "web_page_view": tool_web_page_view,
89
+ "code_agent": tool_code_agent,
90
+ }
91
+
92
+ TOOL_DESCRIPTIONS = """Available tools:
93
+ - web_search: search the web with DuckDuckGo. Input: search query (string).
94
+ - web_page_view: get main text from a web page. Input: URL (string).
95
+ - code_agent: run Python code (math, string ops). Input: code (string)."""
96
+
97
+
98
+ # --- ReAct Agent: Plan -> Act -> Observe -> Reflect ---
99
+ class ReActAgent:
100
+ def __init__(self, token: str | None = None, model: str = LLM_MODEL, max_steps: int = REACT_MAX_STEPS):
101
+ self.token = (token or HF_TOKEN or "").strip()
102
+ self.model = model
103
+ self.max_steps = max_steps
104
+ print("ReActAgent initialized (plan -> act -> observe -> reflect).")
105
+
106
+ def _llm(self, messages: list[dict]) -> str:
107
+ if not self.token:
108
+ return "Error: HF_TOKEN not set. Add your token in .env to use the LLM."
109
+ url = f"https://api-inference.huggingface.co/models/{self.model}"
110
+ headers = {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json"}
111
+ payload = {"inputs": self._messages_to_prompt(messages), "parameters": {"max_new_tokens": 512, "return_full_text": False}}
112
+ try:
113
+ r = requests.post(url, json=payload, headers=headers, timeout=60)
114
+ r.raise_for_status()
115
+ data = r.json()
116
+ if isinstance(data, list) and len(data) > 0:
117
+ return (data[0].get("generated_text") or "").strip()
118
+ if isinstance(data, dict) and "generated_text" in data:
119
+ return (data["generated_text"] or "").strip()
120
+ return ""
121
+ except Exception as e:
122
+ return f"LLM error: {e}"
123
+
124
+ def _messages_to_prompt(self, messages: list[dict]) -> str:
125
+ out = []
126
+ for m in messages:
127
+ role = m.get("role", "user")
128
+ content = m.get("content", "")
129
+ if role == "system":
130
+ out.append(f"System: {content}")
131
+ elif role == "user":
132
+ out.append(f"User: {content}")
133
+ else:
134
+ out.append(f"Assistant: {content}")
135
+ out.append("Assistant:")
136
+ return "\n\n".join(out)
137
+
138
+ def _parse_action(self, text: str) -> tuple[str | None, str | None, str | None]:
139
+ """Returns (thought, action, action_input) or (None, None, final_answer)."""
140
+ text = text.strip()
141
+ final_match = re.search(r"Final Answer\s*:\s*(.+?)(?=\n\n|\Z)", text, re.DOTALL | re.IGNORECASE)
142
+ if final_match:
143
+ return None, None, final_match.group(1).strip()
144
+ action_match = re.search(r"Action\s*:\s*(\w+)", text, re.IGNORECASE)
145
+ input_match = re.search(r"Action Input\s*:\s*(.+?)(?=\n\n|\nThought:|\Z)", text, re.DOTALL | re.IGNORECASE)
146
+ thought = None
147
+ thought_match = re.search(r"Thought\s*:\s*(.+?)(?=\nAction:|\Z)", text, re.DOTALL | re.IGNORECASE)
148
+ if thought_match:
149
+ thought = thought_match.group(1).strip()
150
+ action = action_match.group(1).strip() if action_match else None
151
+ action_input = input_match.group(1).strip() if input_match else None
152
+ if action_input:
153
+ action_input = action_input.strip().strip('"\'')
154
+ return thought, action, action_input
155
 
 
 
 
 
 
156
  def __call__(self, question: str) -> str:
157
+ print(f"ReAct agent received question (first 50 chars): {question[:50]}...")
158
+ if not self.token:
159
+ return "HF_TOKEN not set. Add your Hugging Face token in .env to run the ReAct agent."
160
+ system = (
161
+ "You are a ReAct agent. For each turn you must either:\n"
162
+ "1. Output: Thought: <reasoning> then Action: <tool_name> then Action Input: <input>\n"
163
+ "2. Or when you have the answer: Final Answer: <your answer>\n\n"
164
+ + TOOL_DESCRIPTIONS
165
+ )
166
+ messages = [
167
+ {"role": "system", "content": system},
168
+ {"role": "user", "content": f"Question: {question}\n\nFirst, plan which tool(s) to use, then take action, then observe, then reflect. Give your final answer when done."},
169
+ ]
170
+ for step in range(self.max_steps):
171
+ response = self._llm(messages)
172
+ thought, action, action_input = self._parse_action(response)
173
+ if thought is None and action is None and action_input is not None:
174
+ return action_input # Final Answer
175
+ if not action or action not in TOOLS:
176
+ messages.append({"role": "assistant", "content": response})
177
+ messages.append({"role": "user", "content": "You must use one of the tools (Action: tool_name, Action Input: input) or give Final Answer: your answer. Try again."})
178
+ continue
179
+ try:
180
+ observation = TOOLS[action](action_input)
181
+ except Exception as e:
182
+ observation = f"Tool error: {e}"
183
+ observation = (observation[:3000] + "...") if len(observation) > 3000 else observation
184
+ messages.append({"role": "assistant", "content": response})
185
+ messages.append({"role": "user", "content": f"Observation: {observation}\n\nReflect: does this answer the question? If yes, reply with Final Answer: <answer>. If not, use another tool (Thought / Action / Action Input)."})
186
+ last_assistant = next((m["content"] for m in reversed(messages) if m.get("role") == "assistant"), "")
187
+ final = self._parse_action(last_assistant)
188
+ if final[2] and final[0] is None and final[1] is None:
189
+ return final[2]
190
+ return last_assistant[:500] if last_assistant else "ReAct agent reached max steps without a final answer."
191
 
192
  def run_and_submit_all( profile: gr.OAuthProfile | None):
193
  """
 
210
 
211
  # 1. Instantiate Agent ( modify this part to create your agent)
212
  try:
213
+ agent = ReActAgent(token=os.getenv("HF_TOKEN"), max_steps=REACT_MAX_STEPS)
214
  except Exception as e:
215
  print(f"Error instantiating agent: {e}")
216
  return f"Error initializing agent: {e}", None
 
316
  gr.Markdown(
317
  """
318
  **Instructions:**
 
319
  1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
320
  2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
321
  3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
 
322
  ---
323
  **Disclaimers:**
324
  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).