File size: 17,267 Bytes
9b5b26a
 
 
 
c19d193
6aae614
8fe992b
9b5b26a
 
5df72d6
9b5b26a
 
ca69d52
9b5b26a
ca69d52
9b5b26a
 
ca69d52
 
 
 
 
 
 
 
9b5b26a
 
 
 
ca69d52
 
 
 
 
 
 
 
 
 
9b5b26a
ca69d52
 
 
 
 
8c01ffb
7199aa9
 
 
 
 
 
 
 
ca69d52
 
 
 
 
 
 
 
 
 
 
7199aa9
ca69d52
7199aa9
 
 
 
ca69d52
7199aa9
 
ca69d52
 
 
 
 
 
7199aa9
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7199aa9
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7199aa9
 
 
 
 
 
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7199aa9
 
ca69d52
 
7199aa9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
7199aa9
ca69d52
7199aa9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca69d52
7199aa9
 
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7199aa9
 
ca69d52
 
 
 
 
8c01ffb
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae7a494
ca69d52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae7a494
ca69d52
 
 
e121372
ca69d52
 
 
 
13d500a
8c01ffb
ca69d52
9b5b26a
8c01ffb
861422e
 
9b5b26a
8c01ffb
8fe992b
ca69d52
 
 
 
 
 
 
 
8c01ffb
 
 
 
ca69d52
 
861422e
8fe992b
 
8c01ffb
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
from smolagents import CodeAgent,DuckDuckGoSearchTool, HfApiModel,load_tool,tool
import datetime
import requests
import pytz
import yaml
from tools.final_answer import FinalAnswerTool

from Gradio_UI import GradioUI

# Below is an example of a tool that does nothing. Amaze us with your creativity !
@tool
def get_current_time_in_timezone(timezone: str) -> str:
    """Get the current time in a specified timezone.
    Args:
        timezone: A valid timezone string (e.g., 'America/New_York', 'Asia/Tokyo').
    """
    try:
        # Validate timezone
        if timezone not in pytz.all_timezones:
            return json.dumps({
                "status": "error",
                "message": f"Invalid timezone: '{timezone}'. Please provide a valid timezone like 'America/New_York'.",
                "data_source": "none"
            })
            
        # Create timezone object
        tz = pytz.timezone(timezone)
        # Get current time in that timezone
        local_time = datetime.datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
        
        return json.dumps({
            "status": "success",
            "message": f"The current time in {timezone} is: {local_time}",
            "data": {
                "timezone": timezone,
                "current_time": local_time
            },
            "data_source": "system_time"
        })
    except Exception as e:
        return json.dumps({
            "status": "error",
            "message": f"Error fetching time for timezone '{timezone}': {str(e)}",
            "data_source": "none"
        })

@tool
def get_sunrise_time(location: str, date: str = "today") -> str:
    """Get sunrise time and photography tips for a specified location.
    Args:
        location: Location name or coordinates (e.g., "London" or "51.5074,-0.1278")
        date: Date string (e.g., "2023-12-25" or "today" for current date)
    """
    try:
        # Validate date format if not "today"
        if date != "today":
            try:
                datetime.datetime.strptime(date, "%Y-%m-%d")
                date_param = f"&date={date}"
            except ValueError:
                return json.dumps({
                    "status": "error",
                    "message": f"Invalid date format: '{date}'. Please use YYYY-MM-DD format or 'today'.",
                    "data_source": "none"
                })
        else:
            date_param = ""
            
        # Check if location is in coordinate format
        if "," in location and all(part.replace('.', '', 1).replace('-', '', 1).isdigit() for part in location.split(',')):
            lat, lng = location.split(',')
            location_source = "user_coordinates"
        else:
            # Use Nominatim API for geocoding (convert location name to coordinates)
            try:
                geo_response = requests.get(
                    f"https://nominatim.openstreetmap.org/search?q={location}&format=json&limit=1",
                    headers={"User-Agent": "SunrisePhotographyAssistant/1.0"}
                )
                geo_data = geo_response.json()
                
                if not geo_data:
                    return json.dumps({
                        "status": "error",
                        "message": f"Could not find location: {location}. Please try using coordinates or check the spelling.",
                        "data_source": "none"
                    })
                    
                lat = geo_data[0]['lat']
                lng = geo_data[0]['lon']
                location_source = "openstreetmap_geocoding"
                # Get the properly formatted name from OSM
                location_name = geo_data[0].get('display_name', location)
            except Exception as e:
                return json.dumps({
                    "status": "error",
                    "message": f"Error geocoding location '{location}': {str(e)}",
                    "data_source": "none"
                })
        
        # Use coordinates to get sunrise data
        try:
            response = requests.get(f"https://api.sunrise-sunset.org/json?lat={lat}&lng={lng}{date_param}&formatted=0")
            data = response.json()
            
            if data.get('status') != 'OK':
                return json.dumps({
                    "status": "error",
                    "message": f"Error getting sunrise data: {data.get('status', 'Unknown error')}",
                    "data_source": "none"
                })
                
            # Validate required fields
            required_fields = ['sunrise', 'civil_twilight_begin']
            for field in required_fields:
                if field not in data.get('results', {}):
                    return json.dumps({
                        "status": "error",
                        "message": f"Missing required data field: {field}",
                        "data_source": "none"
                    })
        except Exception as e:
            return json.dumps({
                "status": "error",
                "message": f"Error fetching sunrise data: {str(e)}",
                "data_source": "none"
            })
        
        # Process sunrise times (convert from UTC to local time)
        sunrise_utc = datetime.datetime.fromisoformat(data['results']['sunrise'].replace('Z', '+00:00'))
        civil_twilight_utc = datetime.datetime.fromisoformat(data['results']['civil_twilight_begin'].replace('Z', '+00:00'))
        
        # Get timezone for the location (using timezonefinder library)
        try:
            from timezonefinder import TimezoneFinder
            tf = TimezoneFinder()
            timezone_str = tf.certain_timezone_at(lat=float(lat), lng=float(lng))
            
            if not timezone_str:
                timezone = pytz.UTC
                timezone_name = "UTC"
                timezone_confidence = "low"
                timezone_note = "(Note: Could not determine exact local timezone, using UTC instead)"
            else:
                timezone = pytz.timezone(timezone_str)
                timezone_name = timezone_str
                timezone_confidence = "high"
                timezone_note = ""
        except Exception as e:
            # Fallback to UTC if timezone lookup fails
            timezone = pytz.UTC
            timezone_name = "UTC"
            timezone_confidence = "fallback"
            timezone_note = f"(Note: Error determining timezone: {str(e)}. Using UTC instead)"
            
        # Convert to local time
        sunrise_local = sunrise_utc.astimezone(timezone)
        civil_twilight_local = civil_twilight_utc.astimezone(timezone)
        
        # Calculate golden hour (about 30 minutes after sunrise)
        golden_hour_end = sunrise_local + datetime.timedelta(minutes=30)
        
        # Format times
        sunrise_time = sunrise_local.strftime("%H:%M:%S")
        civil_twilight_time = civil_twilight_local.strftime("%H:%M:%S")
        golden_hour_end_time = golden_hour_end.strftime("%H:%M:%S")
        
        # Provide season-specific advice
        month = sunrise_local.month
        season = ""
        season_tip = ""
        
        if 3 <= month <= 5:  # Spring
            season = "Spring"
            season_tip = "Spring sunrises often feature mist and soft light. Try to capture flowers in the foreground."
        elif 6 <= month <= 8:  # Summer
            season = "Summer"
            season_tip = "Summer sunrise comes early and temperatures rise quickly. Bring insect repellent and watch for lens fog due to humidity."
        elif 9 <= month <= 11:  # Fall/Autumn
            season = "Fall/Autumn"
            season_tip = "Fall sunrises often have fog and interesting cloud formations. Try using fallen leaves as foreground elements."
        else:  # Winter
            season = "Winter"
            season_tip = "Winter sunrises come later with cooler light. Stay warm and bring extra batteries as cold temperatures drain them faster."
        
        # Generate factual data section
        factual_data = [
            f"Sunrise time: {sunrise_time} {timezone_note}",
            f"Civil twilight begins: {civil_twilight_time}",
            f"Golden light ends approximately: {golden_hour_end_time}",
            f"Current season: {season}"
        ]
        
        # Generate photography recommendations
        photography_recommendations = [
            f"Recommended arrival time: Civil twilight ({civil_twilight_time})",
            f"{season} photography consideration: {season_tip}",
            "Suggested gear: Tripod (essential), Variable ND filter, Remote shutter, Extra batteries",
            "Composition tip: Look for interesting foreground elements, don't just focus on the sky",
            "For best exposure, consider bracketing (HDR) or graduated filters"
        ]
        
        # Add a humorous tip
        humor_tips = [
            "Remember to bring coffee, because your eyes might still be dreaming at dawn!",
            "If you see other photographers, don't stand in front of them... unless you want to be the 'silhouette art' in their photos.",
            "If your memory card fills up before the sunrise begins, that's why you should always bring two!",
            "First rule of sunrise photography: Don't oversleep. Second rule: Seriously, don't oversleep!",
            "Remember, the best tripod is the one you forgot at home...",
            "The ultimate test for your sunrise photo: If your mom says 'Wow!' instead of just politely nodding, you've succeeded.",
            "While waiting for sunrise in the frigid morning, remember that your bed misses you too.",
            "If your photos come out blurry, just tell everyone it's an 'artistic style'.",
            "The secret formula for the perfect sunrise shot: Weather forecast accuracy + Luck × Preparation ÷ Snooze button index"
        ]
        
        # Build final output
        result = f"== Sunrise Photography Guide for {location} ==\n\n"
        
        result += "FACTUAL DATA (from sunrise-sunset.org):\n"
        result += "\n".join([f"• {item}" for item in factual_data])
        result += "\n\n"
        
        result += "PHOTOGRAPHY RECOMMENDATIONS:\n"
        result += "\n".join([f"• {item}" for item in photography_recommendations])
        result += "\n\n"
        
        result += "HUMOR TIP:\n"
        result += f"• {random.choice(humor_tips)}\n\n"
        
        result += "NOTE: Weather conditions can significantly affect photography opportunities and are not predicted by this tool."
        
        # Prepare data for structured output
        output_data = {
            "status": "success",
            "message": result,
            "data": {
                "location": location,
                "date": date if date != "today" else datetime.datetime.now().strftime("%Y-%m-%d"),
                "sunrise_time": sunrise_time,
                "civil_twilight_time": civil_twilight_time,
                "golden_hour_end_time": golden_hour_end_time,
                "timezone": timezone_name,
                "season": season
            },
            "data_sources": {
                "location": location_source,
                "astronomy": "sunrise-sunset.org",
                "timezone": f"timezonefinder ({timezone_confidence} confidence)"
            }
        }
        
        return json.dumps(output_data)
        
    except Exception as e:
        return json.dumps({
            "status": "error",
            "message": f"Error processing request: {str(e)}",
            "data_source": "none"
        })

@tool
def get_photography_tip() -> str:
    """Get a random sunrise photography tip."""
    tips = [
        "Arrive 45 minutes before sunrise to set up your equipment and have time to adjust your composition.",
        "Don't just shoot at the moment of sunrise - the light changes before and after are equally worth capturing.",
        "Use a tripod and remote shutter for sharp photos in low light conditions.",
        "Try using foreground elements (trees, rocks, lake reflections) to add depth to your photo.",
        "Use smartphone apps to predict the sunrise direction at your location and plan your composition in advance.",
        "Cloudy days can produce spectacular sunrise photos - don't abandon your shoot just because there's no clear sky.",
        "Light changes rapidly during sunrise - use exposure bracketing (HDR) to ensure you capture enough dynamic range.",
        "Consider using a telephoto lens for sunrise shots to magnify the sun and highlight atmospheric effects near the horizon.",
        "Bring graduated ND filters to balance the bright sky with a darker foreground.",
        "Research your location before traveling, understanding the sun's direction and terrain to find the best shooting spots.",
        "Try shooting panoramas to capture the full range of colors in the sky during sunrise.",
        "Shoot in RAW format to preserve more possibilities for post-processing.",
        "When shooting sunrise at the beach, check tide schedules to plan your best shooting position.",
        "Winter sunrises often have the most dramatic colors and longest golden hour periods.",
        "City sunrises can be spectacular - look for elevated positions that show the city skyline.",
        "In windy weather, add weight to your tripod to prevent camera shake during sunrise shoots.",
        "Use interval shooting (time-lapse) to document the entire sunrise process.",
        "Having the sky occupy 2/3 of your composition often creates more dramatic sunrise photos.",
        "During sunrise, sunlight has a reddish-yellow tone. Try setting your white balance to 'cloudy' to preserve these colors.",
        "If you want to capture star trails and sunrise in one composition, you'll need a star tracker and multiple exposure techniques."
    ]
    
    # Add some humorous "rescue" tips
    rescue_tips = [
        "If you overslept and missed the sunrise, don't worry! Claim you're practicing 'minimalist photography' - not shooting is the ultimate art form.",
        "Lens fogged up? Tell everyone you invented the 'dream filter effect' - it might be trending next week.",
        "If you took 100 photos and none are satisfactory, convert them to black and white - photographers call this 'artistic redemption'.",
        "Forgot your memory card? Congratulations on experiencing 'mindful photography' - recording the moment with your eyes and heart.",
        "Sun hiding behind clouds and not coming out? Claim you were actually there to shoot 'cloud portraits' all along.",
        "Forgot to adjust ISO and got noisy photos? No problem, call it 'digital grain' and claim it's your signature style.",
        "If your sunrise photos look bland, try this pro tip: Add filters until it looks like an alien sunset, then post on social media with #NoFilter.",
        "Tripod collapsed? That's the universe telling you it's time to try 'stream of consciousness photography'."
    ]
    
    # 20% chance to return a "rescue" tip
    if random.random() < 0.2:
        tip = random.choice(rescue_tips)
        tip_type = "humorous_rescue"
    else:
        tip = random.choice(tips)
        tip_type = "practical"
    
    return json.dumps({
        "status": "success",
        "message": tip,
        "data": {
            "tip": tip,
            "tip_type": tip_type
        },
        "data_source": "curated_collection"
    })

@tool
def parse_response(response_json: str) -> str:
    """Helper tool to parse JSON responses and format them for user display.
    Args:
        response_json: JSON string from one of the other tools
    """
    try:
        data = json.loads(response_json)
        
        # Simply return the message for successful responses
        if data.get("status") == "success":
            return data.get("message", "Operation completed successfully, but no message was provided.")
        else:
            # For error responses, provide a clear error message
            return f"ERROR: {data.get('message', 'An unknown error occurred.')}"
            
    except Exception as e:
        return f"Error parsing response: {str(e)}"

final_answer = FinalAnswerTool()

# Model setup
model = HfApiModel(
    max_tokens=2096,
    temperature=0.5,
    model_id='Qwen/Qwen2.5-Coder-32B-Instruct',
    custom_role_conversions=None,
)

# Import image generation tool from Hub
image_generation_tool = load_tool("agents-course/text-to-image", trust_remote_code=True)

with open("prompts.yaml", 'r') as stream:
    prompt_templates = yaml.safe_load(stream)
    
agent = CodeAgent(
    model=model,
    tools=[
        get_current_time_in_timezone,
        get_sunrise_time,
        get_photography_tip,
        parse_response,  # Add helper tool for parsing
        image_generation_tool,
        final_answer
    ],
    max_steps=6,
    verbosity_level=1,
    grammar=None,
    planning_interval=None,
    name="Sunrise Photography Assistant",
    description="An intelligent assistant that helps photographers find the best times for sunrise photography and provides professional advice",
    prompt_templates=prompt_templates
)

GradioUI(agent).launch()