MasterOfHugs commited on
Commit
3711b4f
·
verified ·
1 Parent(s): 8140829

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +253 -41
app.py CHANGED
@@ -1,61 +1,273 @@
1
- import gradio as gr
2
- from smolagents import Tool, CodeAgent
3
- import requests
 
 
 
 
 
 
 
 
 
 
 
4
  import yaml
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- # ---------------------------
7
- # TOOLS
8
- # ---------------------------
9
 
10
- @Tool
11
  def web_search(query: str, max_results: int = 5) -> str:
12
- """Perform a web search (dummy implementation)."""
13
- return f"Search results for '{query}' (top {max_results} results)."
14
 
15
- @Tool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  def visit_webpage(url: str) -> str:
17
- """Visit a webpage and return its HTML content."""
 
 
 
 
 
 
 
 
18
  try:
19
- response = requests.get(url, timeout=5)
20
- return response.text[:2000]
 
 
 
 
 
 
21
  except Exception as e:
22
- return f"Error visiting {url}: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- # ---------------------------
25
- # LOAD CONFIG.YAML
26
- # ---------------------------
27
 
28
- with open("config.yaml", "r") as f:
29
- config = yaml.safe_load(f)
30
 
31
- system_prompt = config.get("system_prompt", "")
32
- prompt_templates = config.get("prompt_templates", {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- # ---------------------------
35
- # AGENT SETUP
36
- # ---------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  agent = CodeAgent(
39
- model="google/flan-t5-small",
40
- tools=[web_search, visit_webpage],
41
  max_steps=6,
 
42
  system_prompt=system_prompt,
43
  prompt_templates=prompt_templates,
 
 
44
  )
45
 
46
- # ---------------------------
47
- # GRADIO UI
48
- # ---------------------------
49
-
50
- def chat_fn(message, history):
51
- response = agent.run(message)
52
- return str(response)
53
-
54
- demo = gr.ChatInterface(
55
- chat_fn,
56
- title="InfoAgent",
57
- description="Posez vos questions factuelles, l'agent cherchera et répondra."
58
- )
59
-
60
  if __name__ == "__main__":
61
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ """
4
+ Minimal, robust app.py for the agent Space.
5
+ - Uses smolagents CodeAgent + HfApiModel (free model google/flan-t5-small)
6
+ - Exposes a few safe tools: web_search, visit_webpage, calculator, get_current_time_in_timezone, final_answer
7
+ - Loads prompts.yaml and normalizes final_answer templates
8
+ - Launches the existing Gradio_UI (Gradio_UI.py)
9
+ """
10
+
11
+ import json
12
+ import re
13
+ import datetime
14
+ import pytz
15
  import yaml
16
+ import traceback
17
+ from typing import Any
18
+
19
+ from smolagents import CodeAgent, HfApiModel, tool
20
+
21
+ # ---- Tools (each tool must have clear docstring describing args) ----
22
+
23
+ @tool
24
+ def final_answer(answer: str) -> str:
25
+ """
26
+ Receive the final answer and return it.
27
+
28
+ Args:
29
+ answer (str): The final answer string produced by the agent.
30
+
31
+ Returns:
32
+ str: The same answer (the framework captures it as the final result).
33
+ """
34
+ return str(answer)
35
+
36
+
37
+ @tool
38
+ def calculator(expr: str) -> str:
39
+ """
40
+ Safely evaluate a simple arithmetic expression.
41
+
42
+ Args:
43
+ expr (str): A math expression like "2 + 3 * (4 - 1)".
44
+
45
+ Returns:
46
+ str: JSON string: {"expression": expr, "result": value} or {"error": "..."}.
47
+ """
48
+ try:
49
+ # whitelist of AST nodes for safety
50
+ import ast, operator
51
+
52
+ allowed = {
53
+ ast.Add: operator.add,
54
+ ast.Sub: operator.sub,
55
+ ast.Mult: operator.mul,
56
+ ast.Div: operator.truediv,
57
+ ast.Pow: operator.pow,
58
+ ast.Mod: operator.mod,
59
+ ast.USub: operator.neg,
60
+ }
61
+
62
+ def _eval(node):
63
+ if isinstance(node, ast.Constant):
64
+ return node.value
65
+ if isinstance(node, ast.Num):
66
+ return node.n
67
+ if isinstance(node, ast.UnaryOp) and type(node.op) in allowed:
68
+ return allowed[type(node.op)](_eval(node.operand))
69
+ if isinstance(node, ast.BinOp) and type(node.op) in allowed:
70
+ return allowed[type(node.op)](_eval(node.left), _eval(node.right))
71
+ raise ValueError("Unsupported expression element: %s" % type(node))
72
+
73
+ tree = ast.parse(expr, mode="eval")
74
+ val = _eval(tree.body)
75
+ return json.dumps({"expression": expr, "result": float(val)})
76
+ except Exception as e:
77
+ return json.dumps({"error": f"Calc error: {e}"})
78
+
79
+
80
+ @tool
81
+ def get_current_time_in_timezone(timezone: str) -> str:
82
+ """
83
+ Get current local time for a timezone.
84
+
85
+ Args:
86
+ timezone (str): IANA timezone string (e.g. "Europe/Paris").
87
+
88
+ Returns:
89
+ str: JSON string {"timezone": tz, "local_time": "..."} or {"error":"..."}.
90
+ """
91
+ try:
92
+ tz = pytz.timezone(timezone)
93
+ local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
94
+ return json.dumps({"timezone": timezone, "local_time": local_time}, ensure_ascii=False)
95
+ except Exception as e:
96
+ return json.dumps({"error": f"Timezone error: {e}"})
97
 
 
 
 
98
 
99
+ @tool
100
  def web_search(query: str, max_results: int = 5) -> str:
101
+ """
102
+ Search the web using duckduckgo_search (best-effort).
103
 
104
+ Args:
105
+ query (str): Search query string.
106
+ max_results (int): Number of results to return (default 5).
107
+
108
+ Returns:
109
+ str: JSON string list of results or {"error": "..."}.
110
+ """
111
+ try:
112
+ # local import so app can start even if ddg lib is missing
113
+ from duckduckgo_search import DDGS
114
+
115
+ with DDGS() as ddgs:
116
+ results = list(ddgs.text(query, max_results=max_results))
117
+ return json.dumps(results, ensure_ascii=False)
118
+ except Exception as e:
119
+ return json.dumps({"error": f"web_search failed: {str(e)}"})
120
+
121
+
122
+ @tool
123
  def visit_webpage(url: str) -> str:
124
+ """
125
+ Download a webpage HTML using requests.
126
+
127
+ Args:
128
+ url (str): The page URL to fetch.
129
+
130
+ Returns:
131
+ str: The page HTML (string) or an error message.
132
+ """
133
  try:
134
+ import requests
135
+
136
+ headers = {
137
+ "User-Agent": "Mozilla/5.0 (compatible; InfoAgent/1.0; +https://example.com)"
138
+ }
139
+ r = requests.get(url, headers=headers, timeout=12)
140
+ r.raise_for_status()
141
+ return r.text
142
  except Exception as e:
143
+ return json.dumps({"error": f"visit_webpage failed: {str(e)}"})
144
+
145
+
146
+ # ---- load prompts.yaml and normalize final_answer template ----
147
+
148
+ DEFAULT_PROMPTS = {
149
+ "system_prompt": (
150
+ "You are an assistant that solves tasks by producing short 'Thought:' then a 'Code:' block "
151
+ "wrapped as:\nCode:\n```py\n# python code\n```\n<end_code>\n"
152
+ "When finished call final_answer(...) with the result.\n"
153
+ ),
154
+ "final_answer": {
155
+ "pre_messages": "You are done. Produce a concise final answer.",
156
+ "post_messages": ""
157
+ },
158
+ }
159
+
160
+ def load_and_normalize_prompts(path: str = "prompts.yaml") -> dict:
161
+ try:
162
+ with open(path, "r", encoding="utf-8") as fh:
163
+ raw = yaml.safe_load(fh) or {}
164
+ except FileNotFoundError:
165
+ raw = {}
166
 
167
+ # extract system_prompt if present
168
+ system_prompt = raw.get("system_prompt", DEFAULT_PROMPTS["system_prompt"])
 
169
 
170
+ # prepare prompt_templates dict: everything except system_prompt
171
+ templates = {k: v for k, v in raw.items() if k != "system_prompt"}
172
 
173
+ # ensure final_answer template exists and both fields are strings (Jinja expects strings)
174
+ fa = templates.get("final_answer")
175
+ if not isinstance(fa, dict):
176
+ fa = {}
177
+ pre = fa.get("pre_messages", DEFAULT_PROMPTS["final_answer"]["pre_messages"])
178
+ post = fa.get("post_messages", DEFAULT_PROMPTS["final_answer"]["post_messages"])
179
+ # If pre/post are lists, join them
180
+ if isinstance(pre, list):
181
+ pre = "\n".join(str(x) for x in pre)
182
+ if isinstance(post, list):
183
+ post = "\n".join(str(x) for x in post)
184
+ templates["final_answer"] = {"pre_messages": str(pre), "post_messages": str(post)}
185
+
186
+ # keep system_prompt included at top-level (CodeAgent expects system_prompt argument separately)
187
+ templates["_system_prompt"] = system_prompt
188
+ return templates
189
+
190
+
191
+ prompt_templates_raw = load_and_normalize_prompts("prompts.yaml")
192
+ system_prompt = prompt_templates_raw.pop("_system_prompt", DEFAULT_PROMPTS["system_prompt"])
193
+ prompt_templates = prompt_templates_raw
194
+
195
+
196
+ # ---- create model (HfApiModel) with fallback ----
197
+
198
+ def create_model(preferred: str = "google/flan-t5-small"):
199
+ try:
200
+ model = HfApiModel(model_id=preferred, max_tokens=1024, temperature=0.0)
201
+ # ensure numeric token counters exist
202
+ try:
203
+ if getattr(model, "last_input_token_count", None) is None:
204
+ model.last_input_token_count = 0
205
+ except Exception:
206
+ pass
207
+ try:
208
+ if getattr(model, "last_output_token_count", None) is None:
209
+ model.last_output_token_count = 0
210
+ except Exception:
211
+ pass
212
+ return model
213
+ except Exception:
214
+ # fallback to a tiny local object implementing run()
215
+ class Fallback:
216
+ def __init__(self):
217
+ self.model_id = "fallback"
218
+ self.last_input_token_count = 0
219
+ self.last_output_token_count = 0
220
 
221
+ def run(self, prompt: str, *args, **kwargs):
222
+ self.last_input_token_count = len(prompt.split()) if prompt else 0
223
+ reply = (
224
+ "FALLBACK: remote model unavailable. I can still use tools: web_search, visit_webpage, "
225
+ "calculator, get_current_time_in_timezone. Ask a concrete question."
226
+ )
227
+ self.last_output_token_count = len(reply.split())
228
+ return reply
229
+
230
+ def __call__(self, *args, **kwargs):
231
+ return self.run(args[0] if args else kwargs.get("prompt", ""))
232
+
233
+ return Fallback()
234
+
235
+
236
+ model = create_model("google/flan-t5-small")
237
+
238
+ # ensure numeric counters not None (protect Gradio_UI)
239
+ try:
240
+ if getattr(model, "last_input_token_count", None) is None:
241
+ model.last_input_token_count = 0
242
+ except Exception:
243
+ pass
244
+ try:
245
+ if getattr(model, "last_output_token_count", None) is None:
246
+ model.last_output_token_count = 0
247
+ except Exception:
248
+ pass
249
+
250
+ # ---- create agent ----
251
+
252
+ tools_list = [final_answer, calculator, get_current_time_in_timezone, web_search, visit_webpage]
253
 
254
  agent = CodeAgent(
255
+ model=model,
256
+ tools=tools_list,
257
  max_steps=6,
258
+ verbosity_level=1,
259
  system_prompt=system_prompt,
260
  prompt_templates=prompt_templates,
261
+ name="MinimalInfoAgent",
262
+ description="Minimal, robust agent with small set of tools",
263
  )
264
 
265
+ # ---- launch UI (uses Gradio_UI.py present in project) ----
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  if __name__ == "__main__":
267
+ try:
268
+ from Gradio_UI import GradioUI
269
+
270
+ GradioUI(agent).launch()
271
+ except Exception as exc:
272
+ print("Failed to launch Gradio UI:", exc)
273
+ traceback.print_exc()