MainStreet123 commited on
Commit
48cfa06
·
verified ·
1 Parent(s): 81917a3

Update app.py

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