File size: 9,149 Bytes
46c0612
 
 
 
 
 
 
 
22f1261
 
46c0612
22f1261
 
 
 
 
 
8ea4b72
22f1261
8fe992b
22f1261
9b5b26a
ad57bc7
22f1261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3401eb9
 
22f1261
 
 
 
 
3401eb9
22f1261
 
9b5b26a
22f1261
 
 
 
 
 
 
 
 
 
 
 
 
3401eb9
22f1261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ea4b72
22f1261
9b5b26a
 
 
 
 
 
 
 
 
 
 
 
8c01ffb
 
8ea4b72
46c0612
8ea4b72
 
ae402b2
 
 
 
 
 
8ea4b72
 
ae402b2
8ea4b72
ad57bc7
ae402b2
8ea4b72
 
 
 
 
 
ad57bc7
 
 
 
8ea4b72
ad57bc7
 
 
 
 
 
 
 
8ea4b72
ae402b2
 
 
 
8ea4b72
ae402b2
 
 
 
 
 
 
 
 
46c0612
ae402b2
8ea4b72
ae402b2
8ea4b72
ae402b2
 
 
 
 
8ea4b72
ae402b2
8ea4b72
ae402b2
 
 
 
 
 
 
 
 
 
 
 
 
 
8ea4b72
 
ae402b2
 
 
 
 
 
 
 
 
8ea4b72
ae402b2
 
 
8ea4b72
ae402b2
 
8ea4b72
ae402b2
 
 
 
8ea4b72
 
6aae614
ae7a494
46c0612
02aa710
46c0612
 
 
22f1261
 
13d500a
8c01ffb
9b5b26a
8c01ffb
 
8fe992b
22f1261
ad57bc7
46c0612
 
ad57bc7
 
 
 
22f1261
ae402b2
 
8c01ffb
 
 
8fe992b
 
8ea4b72
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# ---------------------------------------------------------------------------
# Version-safe model import (handles smolagents < 1.9 and >= 1.14)
# ---------------------------------------------------------------------------
try:
    from smolagents import InferenceClientModel as HFModel
except ImportError:
    from smolagents import HfApiModel as HFModel

from smolagents import (
    CodeAgent,
    DuckDuckGoSearchTool,
    load_tool,
    tool,
)
import datetime
import requests
import pytz
from bs4 import BeautifulSoup
from tools.final_answer import FinalAnswerTool

from Gradio_UI import GradioUI


# ---------------------------------------------------------------------------
# Custom tool 1: Real-time weather (Open-Meteo — free, no API key required)
# ---------------------------------------------------------------------------
@tool
def get_weather(city: str) -> str:
    """Get the current weather for a given city.
    Args:
        city: the name of the city (e.g. 'Paris', 'New York').
    """
    try:
        geo = requests.get(
            "https://geocoding-api.open-meteo.com/v1/search",
            params={"name": city, "count": 1, "language": "en", "format": "json"},
            timeout=15,
        ).json()
        if not geo.get("results"):
            return f"Could not find a city named '{city}'."
        loc = geo["results"][0]
        lat, lon = loc["latitude"], loc["longitude"]

        weather = requests.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": lat,
                "longitude": lon,
                "current_weather": "true",
                "timezone": "auto",
                "temperature_unit": "fahrenheit",
                "windspeed_unit": "mph",
            },
            timeout=15,
        ).json()

        cur = weather.get("current_weather", {})
        temp_f = cur.get("temperature")
        windspeed = cur.get("windspeed")
        wmo = cur.get("weathercode", 0)

        descriptions = {
            0: "clear sky", 1: "mainly clear", 2: "partly cloudy", 3: "overcast",
            45: "fog", 48: "depositing rime fog",
            51: "light drizzle", 53: "moderate drizzle", 55: "dense drizzle",
            61: "slight rain", 63: "moderate rain", 65: "heavy rain",
            71: "slight snow", 73: "moderate snow", 75: "heavy snow",
            80: "slight rain showers", 81: "moderate rain showers", 82: "violent rain showers",
            95: "thunderstorm", 96: "thunderstorm with slight hail", 99: "thunderstorm with heavy hail",
        }
        desc = descriptions.get(wmo, "unknown conditions")

        return (
            f"Current weather in {loc['name']}: {desc}, "
            f"{temp_f}°F, wind {windspeed} mph."
        )
    except Exception as e:
        return f"Error fetching weather for '{city}': {str(e)}"


# ---------------------------------------------------------------------------
# Custom tool 2: Currency converter (free exchange-rate API, no key needed)
# ---------------------------------------------------------------------------
@tool
def convert_currency(amount: float, from_currency: str, to_currency: str) -> str:
    """Convert an amount from one currency to another.
    Args:
        amount: the amount of money to convert.
        from_currency: the source currency code (e.g. 'USD').
        to_currency: the target currency code (e.g. 'EUR').
    """
    try:
        rates = requests.get(
            "https://open.er-api.com/v6/latest",
            params={"base": from_currency.upper()},
            timeout=15,
        ).json()
        if rates.get("result") != "success":
            return f"Could not fetch rates for '{from_currency}'."
        rate = rates["rates"].get(to_currency.upper())
        if rate is None:
            return f"Unknown currency code '{to_currency}'."
        converted = amount * rate
        return (
            f"{amount} {from_currency.upper()} = {converted:.2f} "
            f"{to_currency.upper()} (rate {rate:.4f})"
        )
    except Exception as e:
        return f"Error converting currency: {str(e)}"


# ---------------------------------------------------------------------------
# Custom tool 3: Current time in a timezone
# ---------------------------------------------------------------------------
@tool
def get_current_time_in_timezone(timezone: str) -> str:
    """A tool that fetches the current local time in a specified timezone.
    Args:
        timezone: A string representing a valid timezone (e.g., 'America/New_York').
    """
    try:
        tz = pytz.timezone(timezone)
        local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
        return f"The current local time in {timezone} is: {local_time}"
    except Exception as e:
        return f"Error fetching time for timezone '{timezone}': {str(e)}"


# ---------------------------------------------------------------------------
# Custom tool 4: Browse a webpage (Chrome-mimicking headers)
# ---------------------------------------------------------------------------
@tool
def browse_webpage(url: str) -> str:
    """Fetch a web page and return its readable text content.

    Use this tool AFTER a web search to read the full content of a specific
    result URL. Returns the page title plus its main article text, with
    scripts, navigation, ads, and footers stripped out.

    Args:
        url: The full URL of the web page to read, starting with http:// or https://.
    """
    MAX_CHARS = 5000

    headers = {
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/122.0.0.0 Safari/537.36"
        ),
        "Accept": (
            "text/html,application/xhtml+xml,application/xml;q=0.9,"
            "image/avif,image/webp,image/apng,*/*;q=0.8"
        ),
        "Accept-Language": "en-US,en;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
        "Sec-Fetch-User": "?1",
        "Cache-Control": "max-age=0",
    }

    if not url.startswith(("http://", "https://")):
        url = "https://" + url

    try:
        resp = requests.get(url, headers=headers, timeout=20, allow_redirects=True)
    except requests.exceptions.Timeout:
        return f"Error: request to '{url}' timed out."
    except requests.exceptions.RequestException as e:
        return f"Error: could not reach '{url}': {e}"

    if resp.status_code != 200:
        return (
            f"Error: '{url}' returned HTTP {resp.status_code}. "
            f"The site may be blocking automated access — try a different URL."
        )

    content_type = resp.headers.get("Content-Type", "").lower()

    if "json" in content_type or "text/plain" in content_type:
        text = resp.text
        if len(text) > MAX_CHARS:
            text = text[:MAX_CHARS] + "\n\n[...truncated...]"
        return f"Source: {url}\n\n{text}"

    try:
        soup = BeautifulSoup(resp.text, "html.parser")
    except Exception as e:
        return f"Error parsing HTML from '{url}': {e}"

    title = url
    if soup.title is not None:
        title_text = soup.title.get_text(strip=True)
        if title_text:
            title = title_text

    for tag_name in (
        "script", "style", "noscript", "header", "footer",
        "nav", "aside", "form", "iframe", "svg", "button",
    ):
        for tag in soup.find_all(tag_name):
            tag.decompose()

    main = soup.find("main")
    if main is None:
        main = soup.find("article")
    if main is None:
        main = soup.find(id="content") or soup.find(class_="content")
    if main is None:
        main = soup.body
    if main is None:
        main = soup

    text = main.get_text(separator="\n", strip=True)
    lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
    cleaned = "\n".join(lines)

    if not cleaned:
        return f"Could not extract readable text from '{url}'."

    if len(cleaned) > MAX_CHARS:
        cleaned = cleaned[:MAX_CHARS] + "\n\n[...content truncated...]"

    return f"# {title}\nSource: {url}\n\n{cleaned}"


final_answer = FinalAnswerTool()

# ---------------------------------------------------------------------------
# Model — works with both InferenceClientModel (new) and HfApiModel (old)
# ---------------------------------------------------------------------------
model = HFModel(
    model_id="Qwen/Qwen2.5-Coder-32B-Instruct",
    max_tokens=2096,
    temperature=0.5,
)

image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)

agent = CodeAgent(
    model=model,
    tools=[
        final_answer,
        DuckDuckGoSearchTool(),
        browse_webpage,
        image_generation_tool,
        get_current_time_in_timezone,
        get_weather,
        convert_currency,
    ],
    max_steps=10,
    verbosity_level=2,
    planning_interval=None,
    name=None,
    description=None,
)

GradioUI(agent).launch()