nivakaran commited on
Commit
d436b5a
·
verified ·
1 Parent(s): f100d49

Create utils.py

Browse files
Files changed (1) hide show
  1. src/utils/utils.py +547 -0
src/utils/utils.py ADDED
@@ -0,0 +1,547 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from datetime import datetime
4
+ from typing import Optional, Dict, List, Any
5
+ from pathlib import Path
6
+ from pymongo import MongoClient
7
+ from pymongo.errors import ConnectionFailure, PyMongoError
8
+ from dotenv import load_dotenv
9
+
10
+ from langchain_core.tools import tool
11
+ from langchain_core.messages import HumanMessage
12
+ from src.llms.groqllm import GroqLLM
13
+
14
+ # Load environment variables
15
+ load_dotenv()
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class MongoDBConnection:
22
+ """Singleton MongoDB connection handler"""
23
+ _instance = None
24
+ _client = None
25
+ _db = None
26
+
27
+ def __new__(cls):
28
+ if cls._instance is None:
29
+ cls._instance = super(MongoDBConnection, cls).__new__(cls)
30
+ return cls._instance
31
+
32
+ def __init__(self):
33
+ if self._client is None:
34
+ self._connect()
35
+
36
+ def _connect(self):
37
+ """Initialize MongoDB connection"""
38
+ try:
39
+ mongodb_url = os.getenv('MONGODB_URL') or os.getenv('MONGO_URL') or os.getenv('DATABASE_URL')
40
+
41
+ if not mongodb_url:
42
+ logger.error("No MongoDB URL found in environment variables")
43
+ raise ConnectionError("MongoDB URL not configured")
44
+
45
+ self._client = MongoClient(mongodb_url, serverSelectionTimeoutMS=5000)
46
+
47
+ # Test the connection
48
+ self._client.admin.command('ping')
49
+
50
+ # Get database name from URL or use default
51
+ db_name = os.getenv('MONGODB_DATABASE', 'sparrow_logistics')
52
+ self._db = self._client[db_name]
53
+
54
+ logger.info(f"Successfully connected to MongoDB database: {db_name}")
55
+
56
+ except Exception as e:
57
+ logger.error(f"Failed to connect to MongoDB: {e}")
58
+ raise ConnectionError(f"MongoDB connection failed: {e}")
59
+
60
+ @property
61
+ def db(self):
62
+ """Get database instance"""
63
+ if self._db is None:
64
+ self._connect()
65
+ return self._db
66
+
67
+ @property
68
+ def client(self):
69
+ """Get client instance"""
70
+ if self._client is None:
71
+ self._connect()
72
+ return self._client
73
+
74
+ def test_connection(self) -> bool:
75
+ """Test if MongoDB connection is alive"""
76
+ try:
77
+ self._client.admin.command('ping')
78
+ return True
79
+ except Exception as e:
80
+ logger.error(f"MongoDB connection test failed: {e}")
81
+ return False
82
+
83
+ # Initialize global connection
84
+ mongo_conn = MongoDBConnection()
85
+
86
+ def get_today_str() -> str:
87
+ """Get current date in a human-readable format."""
88
+ return datetime.now().strftime("%a %b %d, %Y")
89
+
90
+ @tool
91
+ def think_tool(reflection: str) -> str:
92
+ """
93
+ Tool for strategic reflection on execution progress and decision-making.
94
+
95
+ Use this tool after each search or database query to analyze results and plan next steps systematically.
96
+ This creates a deliberate pause in customer query execution workflow for quality decision-making.
97
+
98
+ When to use:
99
+ - After receiving database results: What key information did I find?
100
+ - Before deciding next steps: Do I have enough to answer comprehensively?
101
+ - When assessing execution gaps: What specific information am I still missing?
102
+ - Before concluding execution: Can I provide a complete answer now?
103
+
104
+ Args:
105
+ reflection: Your detailed reflection on the execution progress, findings, gaps, and next steps
106
+
107
+ Returns:
108
+ Confirmation that reflection was recorded for decision-making
109
+ """
110
+ logger.info(f"Agent reflection: {reflection}")
111
+ return f"Reflection recorded: {reflection}"
112
+
113
+ @tool(description="Track parcel using tracking number from database")
114
+ def track_package(tracking_number: str) -> str:
115
+ """
116
+ Track customer packages/parcels by looking up tracking information in the database.
117
+
118
+ This tool queries the MongoDB database to find package tracking information,
119
+ delivery status, location updates, and estimated delivery times.
120
+
121
+ Args:
122
+ tracking_number: The tracking number provided by the customer
123
+
124
+ Returns:
125
+ A string describing comprehensive information about the parcel status, location, and delivery details
126
+ """
127
+ try:
128
+ if not tracking_number or not tracking_number.strip():
129
+ return "Error: Please provide a valid tracking number to track your package."
130
+
131
+ tracking_number = tracking_number.strip().upper()
132
+ logger.info(f"Tracking package: {tracking_number}")
133
+
134
+ # Query the packages collection
135
+ db = mongo_conn.db
136
+ packages_collection = db.packages
137
+
138
+ # Find package by tracking number
139
+ package = packages_collection.find_one({
140
+ "$or": [
141
+ {"tracking_number": tracking_number},
142
+ {"tracking_id": tracking_number},
143
+ {"reference_number": tracking_number}
144
+ ]
145
+ })
146
+
147
+ if not package:
148
+ # Also check tracking_history collection for historical data
149
+ tracking_collection = db.tracking_history
150
+ tracking_record = tracking_collection.find_one({
151
+ "tracking_number": tracking_number
152
+ })
153
+
154
+ if tracking_record:
155
+ return f"Found tracking history for {tracking_number}: {tracking_record.get('status', 'Status unknown')}. Last update: {tracking_record.get('last_updated', 'Unknown')}"
156
+
157
+ return f"Sorry, I couldn't find any package with tracking number '{tracking_number}'. Please double-check the tracking number and try again."
158
+
159
+ # Build comprehensive tracking response
160
+ status = package.get('status', 'Unknown')
161
+ location = package.get('current_location', 'Location unknown')
162
+ destination = package.get('destination', 'Not specified')
163
+ estimated_delivery = package.get('estimated_delivery', 'Not available')
164
+ last_updated = package.get('last_updated', 'Unknown')
165
+
166
+ # Get tracking events if available
167
+ tracking_events = package.get('tracking_events', [])
168
+ events_summary = ""
169
+ if tracking_events and len(tracking_events) > 0:
170
+ latest_event = tracking_events[-1] if tracking_events else {}
171
+ events_summary = f" Latest event: {latest_event.get('description', 'No description')} at {latest_event.get('location', 'unknown location')}"
172
+
173
+ response = f"Package {tracking_number}: Status is '{status}'. Currently at: {location}. Destination: {destination}. Estimated delivery: {estimated_delivery}. Last updated: {last_updated}.{events_summary}"
174
+
175
+ logger.info(f"Successfully retrieved tracking info for {tracking_number}")
176
+ return response
177
+
178
+ except PyMongoError as e:
179
+ logger.error(f"Database error while tracking package {tracking_number}: {e}")
180
+ return f"Sorry, I'm having trouble accessing the tracking database right now. Please try again in a moment. (Error: Database connection issue)"
181
+
182
+ except Exception as e:
183
+ logger.error(f"Unexpected error while tracking package {tracking_number}: {e}")
184
+ return f"Sorry, I encountered an unexpected error while tracking your package. Please try again or contact support if the issue persists."
185
+
186
+ @tool(description="Retrieve user information and shipping history from database")
187
+ def get_user_information(user_id: str) -> str:
188
+ """
189
+ Retrieve comprehensive user information including account details, shipping history,
190
+ and preferences from the database.
191
+
192
+ Args:
193
+ user_id: The unique identifier of the user (could be user ID, email, or phone number)
194
+
195
+ Returns:
196
+ A string containing user details, shipping history, preferences, and account status
197
+ """
198
+ try:
199
+ if not user_id or not user_id.strip():
200
+ return "Error: Please provide a valid user ID, email, or phone number to look up user information."
201
+
202
+ user_id = user_id.strip()
203
+ logger.info(f"Looking up user information for: {user_id}")
204
+
205
+ db = mongo_conn.db
206
+ users_collection = db.users
207
+
208
+ # Search for user by various identifiers
209
+ user = users_collection.find_one({
210
+ "$or": [
211
+ {"user_id": user_id},
212
+ {"_id": user_id},
213
+ {"email": user_id},
214
+ {"phone": user_id},
215
+ {"customer_id": user_id}
216
+ ]
217
+ })
218
+
219
+ if not user:
220
+ return f"Sorry, I couldn't find any user account with the identifier '{user_id}'. Please check your user ID, email, or phone number and try again."
221
+
222
+ # Extract user information
223
+ name = user.get('name', user.get('full_name', 'Name not available'))
224
+ email = user.get('email', 'Email not provided')
225
+ phone = user.get('phone', 'Phone not provided')
226
+ account_status = user.get('status', 'Unknown')
227
+ join_date = user.get('created_at', user.get('join_date', 'Unknown'))
228
+
229
+ # Get shipping statistics
230
+ packages_collection = db.packages
231
+ user_packages = list(packages_collection.find({"user_id": user.get('user_id', user.get('_id'))}))
232
+
233
+ total_packages = len(user_packages)
234
+ delivered_packages = len([p for p in user_packages if p.get('status', '').lower() in ['delivered', 'completed']])
235
+ in_transit = len([p for p in user_packages if p.get('status', '').lower() in ['in_transit', 'shipped', 'out_for_delivery']])
236
+
237
+ # Get recent packages
238
+ recent_packages = sorted(user_packages, key=lambda x: x.get('created_at', datetime.min), reverse=True)[:3]
239
+ recent_summary = ""
240
+ if recent_packages:
241
+ recent_list = []
242
+ for pkg in recent_packages:
243
+ pkg_info = f"#{pkg.get('tracking_number', 'No tracking')} ({pkg.get('status', 'Unknown status')})"
244
+ recent_list.append(pkg_info)
245
+ recent_summary = f" Recent packages: {', '.join(recent_list)}."
246
+
247
+ # Check for preferences
248
+ preferences = user.get('preferences', {})
249
+ pref_summary = ""
250
+ if preferences:
251
+ delivery_pref = preferences.get('delivery_preference', 'Standard')
252
+ notification_pref = preferences.get('notifications', 'Email')
253
+ pref_summary = f" Preferences: {delivery_pref} delivery, {notification_pref} notifications."
254
+
255
+ response = (f"User found: {name} ({email}, {phone}). Account status: {account_status}. "
256
+ f"Member since: {join_date}. Shipping history: {total_packages} total packages, "
257
+ f"{delivered_packages} delivered, {in_transit} currently in transit.{recent_summary}{pref_summary}")
258
+
259
+ logger.info(f"Successfully retrieved user info for {user_id}")
260
+ return response
261
+
262
+ except PyMongoError as e:
263
+ logger.error(f"Database error while looking up user {user_id}: {e}")
264
+ return f"Sorry, I'm having trouble accessing the user database right now. Please try again in a moment. (Error: Database connection issue)"
265
+
266
+ except Exception as e:
267
+ logger.error(f"Unexpected error while looking up user {user_id}: {e}")
268
+ return f"Sorry, I encountered an unexpected error while looking up user information. Please try again or contact support if the issue persists."
269
+
270
+ @tool(description="Estimate delivery time between locations using database and routing data")
271
+ def estimated_time_analysis(origin: str, destination: str) -> str:
272
+ """
273
+ Estimate delivery time for a parcel based on origin and destination using
274
+ database routing information, historical data, and current service conditions.
275
+
276
+ Args:
277
+ origin: The starting location/address for the shipment
278
+ destination: The destination location/address for the shipment
279
+
280
+ Returns:
281
+ A string describing the estimated delivery time with detailed breakdown
282
+ """
283
+ try:
284
+ if not origin or not destination or not origin.strip() or not destination.strip():
285
+ return "Error: Please provide both origin and destination locations for delivery time estimation."
286
+
287
+ origin = origin.strip()
288
+ destination = destination.strip()
289
+ logger.info(f"Estimating delivery time from {origin} to {destination}")
290
+
291
+ db = mongo_conn.db
292
+
293
+ # Check routes collection for specific routing data
294
+ routes_collection = db.delivery_routes
295
+ route = routes_collection.find_one({
296
+ "$or": [
297
+ {"origin": {"$regex": origin, "$options": "i"}, "destination": {"$regex": destination, "$options": "i"}},
298
+ {"route_name": {"$regex": f"{origin}.*{destination}", "$options": "i"}}
299
+ ]
300
+ })
301
+
302
+ # Check historical delivery data for similar routes
303
+ packages_collection = db.packages
304
+ historical_deliveries = list(packages_collection.find({
305
+ "origin": {"$regex": origin, "$options": "i"},
306
+ "destination": {"$regex": destination, "$options": "i"},
307
+ "status": {"$in": ["delivered", "completed"]},
308
+ "delivery_time_days": {"$exists": True}
309
+ }).limit(10))
310
+
311
+ # Calculate estimation
312
+ if route:
313
+ estimated_days = route.get('estimated_days', 2)
314
+ service_type = route.get('service_type', 'Standard')
315
+ confidence = "High (based on established route)"
316
+ elif historical_deliveries:
317
+ # Calculate average from historical data
318
+ delivery_times = [d.get('delivery_time_days', 2) for d in historical_deliveries]
319
+ estimated_days = sum(delivery_times) / len(delivery_times)
320
+ service_type = "Standard"
321
+ confidence = f"Medium (based on {len(historical_deliveries)} similar deliveries)"
322
+ else:
323
+ # Fallback estimation based on location analysis
324
+ estimated_days = 2 # Default
325
+ service_type = "Standard"
326
+ confidence = "Low (general estimation)"
327
+
328
+ # Simple distance-based adjustment
329
+ if any(word in destination.lower() for word in ['international', 'overseas', 'abroad']):
330
+ estimated_days = 7
331
+ service_type = "International"
332
+ elif any(word in destination.lower() for word in ['express', 'priority', 'urgent']):
333
+ estimated_days = 1
334
+ service_type = "Express"
335
+
336
+ # Check for current service alerts
337
+ alerts_collection = db.service_alerts
338
+ current_alerts = list(alerts_collection.find({
339
+ "status": "active",
340
+ "$or": [
341
+ {"affected_locations": {"$regex": origin, "$options": "i"}},
342
+ {"affected_locations": {"$regex": destination, "$options": "i"}}
343
+ ]
344
+ }))
345
+
346
+ alert_info = ""
347
+ if current_alerts:
348
+ delay_info = []
349
+ for alert in current_alerts[:2]: # Show max 2 alerts
350
+ alert_desc = alert.get('description', 'Service delay')
351
+ additional_days = alert.get('estimated_delay_days', 1)
352
+ delay_info.append(f"{alert_desc} (+{additional_days} days)")
353
+ estimated_days += additional_days
354
+
355
+ alert_info = f" Current alerts affecting delivery: {'; '.join(delay_info)}."
356
+
357
+ # Format the response
358
+ estimated_days = round(estimated_days, 1)
359
+ business_days = max(1, int(estimated_days))
360
+
361
+ response = (f"Estimated delivery time from {origin} to {destination}: {estimated_days} days "
362
+ f"({business_days} business days) via {service_type} service. "
363
+ f"Confidence level: {confidence}.{alert_info}")
364
+
365
+ logger.info(f"Successfully calculated delivery estimate: {origin} → {destination} = {estimated_days} days")
366
+ return response
367
+
368
+ except PyMongoError as e:
369
+ logger.error(f"Database error while calculating delivery estimate: {e}")
370
+ return f"Sorry, I'm having trouble accessing the delivery routing database. Using general estimate: 2-3 business days for standard delivery. Please try again later for more accurate timing."
371
+
372
+ except Exception as e:
373
+ logger.error(f"Unexpected error during delivery estimation: {e}")
374
+ return f"Sorry, I encountered an unexpected error while calculating delivery time. General estimate: 2-3 business days for standard delivery from {origin} to {destination}."
375
+
376
+ @tool(description="Search for packages and shipments based on various criteria")
377
+ def search_packages(search_criteria: str) -> str:
378
+ """
379
+ Search for packages in the database using flexible criteria like user name,
380
+ destination, status, date range, or other package attributes.
381
+
382
+ Args:
383
+ search_criteria: Search terms or criteria (e.g., "user John", "status delivered", "destination New York")
384
+
385
+ Returns:
386
+ A string containing search results with package information
387
+ """
388
+ try:
389
+ if not search_criteria or not search_criteria.strip():
390
+ return "Error: Please provide search criteria to find packages."
391
+
392
+ criteria = search_criteria.strip().lower()
393
+ logger.info(f"Searching packages with criteria: {criteria}")
394
+
395
+ db = mongo_conn.db
396
+ packages_collection = db.packages
397
+
398
+ # Build search query based on criteria
399
+ search_query = {}
400
+
401
+ # Parse search criteria
402
+ if "status" in criteria:
403
+ status_terms = ["delivered", "in_transit", "pending", "shipped", "out_for_delivery"]
404
+ for status in status_terms:
405
+ if status in criteria:
406
+ search_query["status"] = {"$regex": status, "$options": "i"}
407
+ break
408
+
409
+ if "user" in criteria or "customer" in criteria:
410
+ # Extract user identifier after "user" keyword
411
+ user_part = criteria.split("user")[-1].strip()
412
+ if user_part:
413
+ search_query["$or"] = [
414
+ {"user_id": {"$regex": user_part, "$options": "i"}},
415
+ {"customer_name": {"$regex": user_part, "$options": "i"}},
416
+ {"recipient_name": {"$regex": user_part, "$options": "i"}}
417
+ ]
418
+
419
+ if "destination" in criteria or "to" in criteria:
420
+ dest_part = criteria.split("destination")[-1].strip() if "destination" in criteria else criteria.split("to")[-1].strip()
421
+ if dest_part:
422
+ search_query["destination"] = {"$regex": dest_part, "$options": "i"}
423
+
424
+ if "origin" in criteria or "from" in criteria:
425
+ origin_part = criteria.split("origin")[-1].strip() if "origin" in criteria else criteria.split("from")[-1].strip()
426
+ if origin_part:
427
+ search_query["origin"] = {"$regex": origin_part, "$options": "i"}
428
+
429
+ # If no specific criteria matched, do a general text search
430
+ if not search_query:
431
+ search_query = {
432
+ "$or": [
433
+ {"tracking_number": {"$regex": criteria, "$options": "i"}},
434
+ {"customer_name": {"$regex": criteria, "$options": "i"}},
435
+ {"destination": {"$regex": criteria, "$options": "i"}},
436
+ {"origin": {"$regex": criteria, "$options": "i"}},
437
+ {"description": {"$regex": criteria, "$options": "i"}}
438
+ ]
439
+ }
440
+
441
+ # Execute search
442
+ results = list(packages_collection.find(search_query).limit(10))
443
+
444
+ if not results:
445
+ return f"No packages found matching '{search_criteria}'. Please try different search terms or check if the information is correct."
446
+
447
+ # Format results
448
+ response_parts = [f"Found {len(results)} package(s) matching '{search_criteria}':"]
449
+
450
+ for i, package in enumerate(results, 1):
451
+ tracking_num = package.get('tracking_number', 'No tracking')
452
+ status = package.get('status', 'Unknown')
453
+ destination = package.get('destination', 'Unknown destination')
454
+ customer = package.get('customer_name', package.get('recipient_name', 'Unknown customer'))
455
+
456
+ response_parts.append(f"{i}. #{tracking_num} - {customer} → {destination} (Status: {status})")
457
+
458
+ logger.info(f"Search completed: found {len(results)} packages")
459
+ return "\n".join(response_parts)
460
+
461
+ except PyMongoError as e:
462
+ logger.error(f"Database error during package search: {e}")
463
+ return f"Sorry, I'm having trouble searching the package database right now. Please try again in a moment."
464
+
465
+ except Exception as e:
466
+ logger.error(f"Unexpected error during package search: {e}")
467
+ return f"Sorry, I encountered an unexpected error while searching for packages. Please try again."
468
+
469
+ @tool(description="Get service alerts and operational updates")
470
+ def get_service_alerts(location: str = "") -> str:
471
+ """
472
+ Retrieve current service alerts, delays, and operational updates that might affect deliveries.
473
+
474
+ Args:
475
+ location: Optional location to filter alerts (e.g., "New York", "California")
476
+
477
+ Returns:
478
+ A string containing current service alerts and operational information
479
+ """
480
+ try:
481
+ logger.info(f"Getting service alerts for location: {location or 'all locations'}")
482
+
483
+ db = mongo_conn.db
484
+ alerts_collection = db.service_alerts
485
+
486
+ # Build query
487
+ query = {"status": "active"}
488
+ if location and location.strip():
489
+ query["$or"] = [
490
+ {"affected_locations": {"$regex": location.strip(), "$options": "i"}},
491
+ {"title": {"$regex": location.strip(), "$options": "i"}},
492
+ {"description": {"$regex": location.strip(), "$options": "i"}}
493
+ ]
494
+
495
+ alerts = list(alerts_collection.find(query).sort("priority", -1).limit(5))
496
+
497
+ if not alerts:
498
+ location_msg = f" for {location}" if location else ""
499
+ return f"Good news! There are currently no active service alerts{location_msg}. All services are operating normally."
500
+
501
+ # Format alerts
502
+ response_parts = []
503
+ location_msg = f" affecting {location}" if location else ""
504
+ response_parts.append(f"Current service alerts{location_msg}:")
505
+
506
+ for i, alert in enumerate(alerts, 1):
507
+ title = alert.get('title', 'Service Alert')
508
+ description = alert.get('description', 'No details available')
509
+ severity = alert.get('severity', 'Medium')
510
+ affected_locations = alert.get('affected_locations', [])
511
+ estimated_resolution = alert.get('estimated_resolution', 'Unknown')
512
+
513
+ location_info = ""
514
+ if affected_locations:
515
+ if isinstance(affected_locations, list):
516
+ location_info = f" (Affects: {', '.join(affected_locations[:3])})"
517
+ else:
518
+ location_info = f" (Affects: {affected_locations})"
519
+
520
+ response_parts.append(f"{i}. [{severity}] {title}{location_info}")
521
+ response_parts.append(f" {description}")
522
+ if estimated_resolution != 'Unknown':
523
+ response_parts.append(f" Expected resolution: {estimated_resolution}")
524
+
525
+ logger.info(f"Retrieved {len(alerts)} service alerts")
526
+ return "\n".join(response_parts)
527
+
528
+ except PyMongoError as e:
529
+ logger.error(f"Database error while getting service alerts: {e}")
530
+ return "Sorry, I'm having trouble accessing service alert information right now. Please check our website or contact support for current service status."
531
+
532
+ except Exception as e:
533
+ logger.error(f"Unexpected error while getting service alerts: {e}")
534
+ return "Sorry, I encountered an unexpected error while retrieving service alerts. Please try again later."
535
+
536
+ # List of all available tools
537
+ tools = [
538
+ think_tool,
539
+ track_package,
540
+ get_user_information,
541
+ estimated_time_analysis,
542
+ search_packages,
543
+ get_service_alerts
544
+ ]
545
+
546
+ # Tools by name for easy access
547
+ tools_by_name = {tool.name: tool for tool in tools}