from typing import Any from smolagents.tools import Tool import requests from datetime import datetime, timedelta import json import os from dotenv import load_dotenv import re import anthropic class CountryInfoTool(Tool): name = "country_info" description = "Retrieves important contextual information about a country in real-time: security, current events, national holidays, political climate, travel advice." inputs = { 'country': {'type': 'string', 'description': 'Country name in French or English (e.g., "France", "United States", "Japan")'}, 'info_type': {'type': 'string', 'description': 'Type of information requested: "all" (recommended), "security", "events", "holidays", "travel", "politics"', 'nullable': True} } output_type = "string" def __init__(self): super().__init__() load_dotenv() # Initialiser le client Claude (Anthropic) self.claude_client = anthropic.Anthropic(api_key=os.getenv('ANTROPIC_KEY')) # Mapping étendu des pays français vers anglais pour les APIs self.country_mapping = { # Europe 'france': 'France', 'allemagne': 'Germany', 'italie': 'Italy', 'espagne': 'Spain', 'royaume-uni': 'United Kingdom', 'angleterre': 'United Kingdom', 'écosse': 'United Kingdom', 'pays-bas': 'Netherlands', 'hollande': 'Netherlands', 'belgique': 'Belgium', 'suisse': 'Switzerland', 'autriche': 'Austria', 'portugal': 'Portugal', 'suède': 'Sweden', 'norvège': 'Norway', 'danemark': 'Denmark', 'finlande': 'Finland', 'pologne': 'Poland', 'république tchèque': 'Czech Republic', 'tchéquie': 'Czech Republic', 'hongrie': 'Hungary', 'roumanie': 'Romania', 'bulgarie': 'Bulgaria', 'grèce': 'Greece', 'croatie': 'Croatia', 'slovénie': 'Slovenia', 'slovaquie': 'Slovakia', 'estonie': 'Estonia', 'lettonie': 'Latvia', 'lituanie': 'Lithuania', 'irlande': 'Ireland', 'islande': 'Iceland', 'malte': 'Malta', 'chypre': 'Cyprus', 'serbie': 'Serbia', 'bosnie': 'Bosnia and Herzegovina', 'monténégro': 'Montenegro', 'macédoine': 'North Macedonia', 'albanie': 'Albania', 'moldavie': 'Moldova', 'ukraine': 'Ukraine', 'biélorussie': 'Belarus', 'russie': 'Russia', # Amériques 'états-unis': 'United States', 'usa': 'United States', 'amérique': 'United States', 'canada': 'Canada', 'mexique': 'Mexico', 'brésil': 'Brazil', 'argentine': 'Argentina', 'chili': 'Chile', 'pérou': 'Peru', 'colombie': 'Colombia', 'venezuela': 'Venezuela', 'équateur': 'Ecuador', 'bolivie': 'Bolivia', 'paraguay': 'Paraguay', 'uruguay': 'Uruguay', 'guatemala': 'Guatemala', 'costa rica': 'Costa Rica', 'panama': 'Panama', 'cuba': 'Cuba', 'jamaïque': 'Jamaica', 'haïti': 'Haiti', 'république dominicaine': 'Dominican Republic', # Asie 'chine': 'China', 'japon': 'Japan', 'corée du sud': 'South Korea', 'corée du nord': 'North Korea', 'inde': 'India', 'pakistan': 'Pakistan', 'bangladesh': 'Bangladesh', 'sri lanka': 'Sri Lanka', 'thaïlande': 'Thailand', 'vietnam': 'Vietnam', 'cambodge': 'Cambodia', 'laos': 'Laos', 'myanmar': 'Myanmar', 'birmanie': 'Myanmar', 'malaisie': 'Malaysia', 'singapour': 'Singapore', 'indonésie': 'Indonesia', 'philippines': 'Philippines', 'brunei': 'Brunei', 'mongolie': 'Mongolia', 'kazakhstan': 'Kazakhstan', 'ouzbékistan': 'Uzbekistan', 'kirghizistan': 'Kyrgyzstan', 'tadjikistan': 'Tajikistan', 'turkménistan': 'Turkmenistan', 'afghanistan': 'Afghanistan', 'iran': 'Iran', 'irak': 'Iraq', 'syrie': 'Syria', 'turquie': 'Turkey', 'israël': 'Israel', 'palestine': 'Palestine', 'liban': 'Lebanon', 'jordanie': 'Jordan', 'arabie saoudite': 'Saudi Arabia', 'émirats arabes unis': 'United Arab Emirates', 'qatar': 'Qatar', 'koweït': 'Kuwait', 'bahreïn': 'Bahrain', 'oman': 'Oman', 'yémen': 'Yemen', # Afrique 'maroc': 'Morocco', 'algérie': 'Algeria', 'tunisie': 'Tunisia', 'libye': 'Libya', 'égypte': 'Egypt', 'soudan': 'Sudan', 'éthiopie': 'Ethiopia', 'kenya': 'Kenya', 'tanzanie': 'Tanzania', 'ouganda': 'Uganda', 'rwanda': 'Rwanda', 'burundi': 'Burundi', 'congo': 'Democratic Republic of the Congo', 'république démocratique du congo': 'Democratic Republic of the Congo', 'rdc': 'Democratic Republic of the Congo', 'république du congo': 'Republic of the Congo', 'cameroun': 'Cameroon', 'nigeria': 'Nigeria', 'ghana': 'Ghana', 'côte d\'ivoire': 'Ivory Coast', 'sénégal': 'Senegal', 'mali': 'Mali', 'burkina faso': 'Burkina Faso', 'niger': 'Niger', 'tchad': 'Chad', 'centrafrique': 'Central African Republic', 'gabon': 'Gabon', 'guinée équatoriale': 'Equatorial Guinea', 'sao tomé': 'Sao Tome and Principe', 'cap-vert': 'Cape Verde', 'guinée-bissau': 'Guinea-Bissau', 'guinée': 'Guinea', 'sierra leone': 'Sierra Leone', 'liberia': 'Liberia', 'togo': 'Togo', 'bénin': 'Benin', 'mauritanie': 'Mauritania', 'gambie': 'Gambia', 'afrique du sud': 'South Africa', 'namibie': 'Namibia', 'botswana': 'Botswana', 'zimbabwe': 'Zimbabwe', 'zambie': 'Zambia', 'malawi': 'Malawi', 'mozambique': 'Mozambique', 'madagascar': 'Madagascar', 'maurice': 'Mauritius', 'seychelles': 'Seychelles', 'comores': 'Comoros', 'djibouti': 'Djibouti', 'érythrée': 'Eritrea', 'somalie': 'Somalia', 'lesotho': 'Lesotho', 'eswatini': 'Eswatini', 'swaziland': 'Eswatini', # Océanie 'australie': 'Australia', 'nouvelle-zélande': 'New Zealand', 'fidji': 'Fiji', 'papouasie-nouvelle-guinée': 'Papua New Guinea', 'vanuatu': 'Vanuatu', 'samoa': 'Samoa', 'tonga': 'Tonga', 'îles salomon': 'Solomon Islands', 'micronésie': 'Micronesia', 'palau': 'Palau', 'nauru': 'Nauru', 'kiribati': 'Kiribati', 'tuvalu': 'Tuvalu' } # Codes ISO pour certaines APIs self.country_codes = { 'France': 'FR', 'United States': 'US', 'United Kingdom': 'GB', 'Germany': 'DE', 'Italy': 'IT', 'Spain': 'ES', 'Japan': 'JP', 'China': 'CN', 'India': 'IN', 'Brazil': 'BR', 'Canada': 'CA', 'Australia': 'AU', 'Russia': 'RU', 'Mexico': 'MX', 'South Korea': 'KR', 'Netherlands': 'NL', 'Belgium': 'BE', 'Switzerland': 'CH', 'Sweden': 'SE', 'Norway': 'NO', 'Denmark': 'DK', 'Turkey': 'TR', 'Egypt': 'EG', 'Thailand': 'TH', 'Iran': 'IR' } def forward(self, country: str, info_type: str = "all") -> str: try: # Normaliser le nom du pays country_normalized = self._normalize_country_name(country) if not country_normalized: return f"❌ Country not recognized: '{country}'. Try with the full name (e.g., 'France', 'United States', 'United Kingdom')" # Collecter les informations selon le type demandé info_sections = [] if info_type in ["all", "security"]: security_info = self._get_security_info(country_normalized) if security_info: info_sections.append(security_info) if info_type in ["all", "events"]: events_info = self._get_current_events_info(country_normalized) if events_info: info_sections.append(events_info) if info_type in ["all", "holidays"]: holidays_info = self._get_holidays_info(country_normalized) if holidays_info: info_sections.append(holidays_info) if info_type in ["all", "travel"]: travel_info = self._get_travel_info(country_normalized) if travel_info: info_sections.append(travel_info) if info_type in ["all", "politics"]: politics_info = self._get_political_info(country_normalized) if politics_info: info_sections.append(politics_info) if not info_sections: return f"❌ No information available for {country_normalized} currently." # Assembler le rapport final result = f"🌍 **Contextual Information for {country_normalized}**\n" result += f"*Updated: {datetime.now().strftime('%m/%d/%Y %H:%M')}*\n\n" result += "\n\n".join(info_sections) # Ajouter une recommandation finale intelligente si Claude est disponible et qu'on demande toutes les infos if info_type == "all" and self.claude_client: final_recommendation = self._get_llm_final_recommendation(country_normalized, "\n\n".join(info_sections)) if final_recommendation: result += f"\n\n{final_recommendation}" return result except Exception as e: return f"❌ Error retrieving information: {str(e)}" def _normalize_country_name(self, country: str): """Normalise le nom du pays""" country_lower = country.lower().strip() # Vérifier dans le mapping français -> anglais if country_lower in self.country_mapping: return self.country_mapping[country_lower] # Vérifier si c'est déjà un nom anglais valide for french, english in self.country_mapping.items(): if country_lower == english.lower(): return english # Essayer une correspondance partielle for french, english in self.country_mapping.items(): if country_lower in french or french in country_lower: return english # Si pas trouvé dans le mapping, essayer de valider via l'API REST Countries validated_country = self._validate_country_via_api(country) if validated_country: return validated_country return None def _validate_country_via_api(self, country: str): """Valide et normalise le nom du pays via l'API REST Countries""" try: # Essayer d'abord avec le nom exact url = f"https://restcountries.com/v3.1/name/{country}" response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() if data: # Retourner le nom officiel en anglais return data[0].get('name', {}).get('common', country.title()) # Si échec, essayer avec une recherche partielle url = f"https://restcountries.com/v3.1/name/{country}?fullText=false" response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() if data: # Prendre le premier résultat return data[0].get('name', {}).get('common', country.title()) return None except Exception: # En cas d'erreur, retourner le nom avec la première lettre en majuscule return country.title() if len(country) > 2 else None def _get_country_code_from_api(self, country: str): """Récupère le code ISO du pays via l'API REST Countries""" try: url = f"https://restcountries.com/v3.1/name/{country}" response = requests.get(url, timeout=5) if response.status_code == 200: data = response.json() if data: # Retourner le code ISO alpha-2 return data[0].get('cca2', '') return None except Exception: return None def _get_security_info(self, country: str) -> str: """Récupère les informations de sécurité avec recherche exhaustive""" try: # Vérifier d'abord si c'est un pays à risque connu risk_level = self._check_known_risk_countries(country) # Recherches multiples avec différents mots-clés all_news_data = [] # Recherche 1: Sécurité générale security_keywords = f"{country} travel advisory security warning conflict war" news_data1 = self._search_security_news(security_keywords) all_news_data.extend(news_data1) # Recherche 2: Conflits spécifiques conflict_keywords = f"{country} war conflict violence terrorism attack bombing" news_data2 = self._search_security_news(conflict_keywords) all_news_data.extend(news_data2) # Recherche 3: Instabilité politique political_keywords = f"{country} coup government crisis instability sanctions" news_data3 = self._search_security_news(political_keywords) all_news_data.extend(news_data3) # Recherche 4: Alertes de voyage travel_keywords = f"{country} 'travel ban' 'do not travel' 'avoid travel' embassy" news_data4 = self._search_security_news(travel_keywords) all_news_data.extend(news_data4) # Supprimer les doublons unique_news = [] seen_titles = set() for article in all_news_data: title = article.get('title', '') if title and title not in seen_titles: unique_news.append(article) seen_titles.add(title) # Analyser les résultats pour déterminer le niveau de sécurité security_level, description, recommendation = self._analyze_security_data(country, unique_news, risk_level) result = f"🛡️ **Security and Travel Advice**\n" result += f"{security_level} **Level determined by real-time analysis**\n" result += f"📋 {description}\n" result += f"🎯 **Recommendation: {recommendation}**" return result except Exception as e: return f"🛡️ **Security**: Error during retrieval - {str(e)}" def _check_known_risk_countries(self, country: str) -> str: """Vérifie si le pays est dans la liste des pays à risque connus""" # Pays à très haut risque (guerre active, conflit majeur) high_risk_countries = [ 'Ukraine', 'Afghanistan', 'Syria', 'Yemen', 'Somalia', 'South Sudan', 'Central African Republic', 'Mali', 'Burkina Faso', 'Niger', 'Democratic Republic of the Congo', 'Myanmar', 'Palestine', 'Gaza', 'West Bank', 'Iraq', 'Libya', 'Sudan' ] # Pays à risque modéré (instabilité, tensions) moderate_risk_countries = [ 'Iran', 'North Korea', 'Venezuela', 'Belarus', 'Ethiopia', 'Chad', 'Cameroon', 'Nigeria', 'Pakistan', 'Bangladesh', 'Haiti', 'Lebanon', 'Turkey', 'Egypt', 'Algeria' ] # Pays avec tensions spécifiques tension_countries = [ 'Russia', 'China', 'Israel', 'India', 'Kashmir', 'Taiwan', 'Hong Kong', 'Thailand', 'Philippines', 'Colombia' ] country_lower = country.lower() for risk_country in high_risk_countries: if risk_country.lower() in country_lower or country_lower in risk_country.lower(): return "HIGH_RISK" for risk_country in moderate_risk_countries: if risk_country.lower() in country_lower or country_lower in risk_country.lower(): return "MODERATE_RISK" for risk_country in tension_countries: if risk_country.lower() in country_lower or country_lower in risk_country.lower(): return "TENSION" return "UNKNOWN" def _search_security_news(self, keywords: str) -> list: """Recherche d'actualités de sécurité avec période étendue""" try: # Utiliser NewsAPI si disponible api_key = os.getenv('NEWSAPI_KEY') if api_key: url = "https://newsapi.org/v2/everything" params = { 'q': keywords, 'sortBy': 'publishedAt', 'pageSize': 20, # Plus d'articles 'language': 'en', 'from': (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d'), # 30 jours au lieu de 7 'apiKey': api_key } response = requests.get(url, params=params, timeout=10) if response.status_code == 200: data = response.json() articles = data.get('articles', []) # Filtrer les articles pertinents relevant_articles = [] for article in articles: title = article.get('title', '').lower() description = article.get('description', '').lower() # Mots-clés critiques pour filtrer critical_keywords = ['war', 'conflict', 'attack', 'bombing', 'terrorism', 'violence', 'crisis', 'coup', 'sanctions', 'advisory', 'warning', 'danger', 'risk', 'threat', 'security'] if any(keyword in title or keyword in description for keyword in critical_keywords): relevant_articles.append(article) return relevant_articles # Fallback: recherche via une API publique alternative return self._search_alternative_news(keywords) except Exception: return [] def _search_alternative_news(self, keywords: str) -> list: """Recherche alternative sans API key""" try: # Utiliser une API publique comme Guardian ou BBC # Pour l'exemple, on simule une recherche basique dangerous_keywords = ['war', 'conflict', 'terrorism', 'violence', 'crisis', 'coup', 'sanctions'] warning_keywords = ['protest', 'unrest', 'advisory', 'caution', 'alert'] # Simulation basée sur les mots-clés (à remplacer par vraie API) if any(word in keywords.lower() for word in dangerous_keywords): return [{'title': f'Security concerns in {keywords.split()[0]}', 'description': 'Recent security developments'}] elif any(word in keywords.lower() for word in warning_keywords): return [{'title': f'Travel advisory for {keywords.split()[0]}', 'description': 'Caution advised'}] return [] except Exception: return [] def _analyze_security_data(self, country: str, news_data: list, risk_level: str = "UNKNOWN") -> tuple: """Analyse les données de sécurité avec Claude uniquement""" try: if not self.claude_client: return ("⚪", "Claude not available", "❓ Anthropic API key required for security analysis") # Préparer le contenu des actualités pour l'analyse news_content = "" for i, article in enumerate(news_data[:10], 1): # Limiter à 10 articles title = article.get('title', '') description = article.get('description', '') if title or description: news_content += f"{i}. {title}\n{description}\n\n" # Si pas d'actualités mais pays à haut risque connu, forcer l'analyse if not news_content.strip(): if risk_level == "HIGH_RISK": return ("🔴", f"Very high risk country - active conflict or war", "🚫 CHANGE DESTINATION - Active conflict zone") elif risk_level == "MODERATE_RISK": return ("🟡", f"Moderate risk country - political instability", "⚠️ Travel possible with enhanced precautions") else: return ("🟢", f"No recent security news found", "✅ Destination considered safe") # Utiliser Claude pour analyser analysis = self._llm_security_analysis(country, news_content, risk_level) if analysis: return analysis else: return ("⚪", "Claude analysis error", "❓ Unable to analyze security currently") except Exception: return ("⚪", "Analysis impossible", "❓ Consult official sources") def _llm_security_analysis(self, country: str, news_content: str, risk_level: str = "UNKNOWN"): """Utilise Claude pour analyser la sécurité du pays""" try: if not self.claude_client: return None prompt = f"""Analyze the following recent news about {country} and determine the security level for a traveler: KNOWN RISK LEVEL: {risk_level} - HIGH_RISK = Country in active war or major conflict - MODERATE_RISK = Country with significant political instability - TENSION = Country with geopolitical tensions - UNKNOWN = No special classification RECENT NEWS: {news_content} CRITICAL INSTRUCTIONS: 1. If RISK LEVEL = HIGH_RISK, you MUST recommend CHANGE_DESTINATION unless clear evidence of improvement 2. For Ukraine, Palestine, Afghanistan, Syria, Yemen: ALWAYS RED/CHANGE_DESTINATION 3. Analyze risk level for a civilian tourist/traveler 4. Be VERY STRICT - traveler safety is priority Respond ONLY in the following JSON format: {{ "level": "RED|YELLOW|GREEN", "description": "Short situation description (max 100 characters)", "recommendation": "CHANGE_DESTINATION|ENHANCED_PRECAUTIONS|SAFE_DESTINATION", "justification": "Explanation of your decision (max 200 characters)" }} STRICT Criteria: - RED/CHANGE_DESTINATION: active war, armed conflict, active terrorism, coup, widespread violence, combat zones - YELLOW/ENHANCED_PRECAUTIONS: violent protests, very high crime, political instability, ethnic tensions - GREEN/SAFE_DESTINATION: no major risks for civilians ABSOLUTE PRIORITY: Protect travelers - when in doubt, choose the strictest security level.""" response = self.claude_client.messages.create( model="claude-3-opus-20240229", max_tokens=300, temperature=0.1, system="Vous êtes un expert en sécurité des voyages. Analysez objectivement les risques.", messages=[ {"role": "user", "content": prompt} ] ) result_text = response.content[0].text.strip() # Parser la réponse JSON try: result = json.loads(result_text) level = result.get('level', 'GREEN') description = result.get('description', 'Analysis completed') recommendation = result.get('recommendation', 'SAFE_DESTINATION') justification = result.get('justification', '') # Convertir en format attendu if level == 'RED': emoji = "🔴" advice = "🚫 CHANGE DESTINATION - " + justification elif level == 'YELLOW': emoji = "🟡" advice = "⚠️ Travel possible with enhanced precautions - " + justification else: emoji = "🟢" advice = "✅ Destination considered safe - " + justification return (emoji, description, advice) except json.JSONDecodeError: return None except Exception: return None def _get_current_events_info(self, country: str) -> str: """Retrieves current events via web search""" try: # Search for recent events events_keywords = f"{country} current events news today recent" events_data = self._search_current_events(events_keywords) if not events_data: return f"📅 **Events**: No major events detected for {country}" result = f"📅 **Current Events and Context**\n" for i, event in enumerate(events_data[:5], 1): title = event.get('title', 'Event not specified') result += f"• {title}\n" return result.rstrip() except Exception: return "📅 **Events**: Error during retrieval" def _search_current_events(self, keywords: str) -> list: """Recherche d'événements actuels""" try: # Utiliser NewsAPI si disponible api_key = os.getenv('NEWSAPI_KEY') if api_key: url = "https://newsapi.org/v2/everything" params = { 'q': keywords, 'sortBy': 'publishedAt', 'pageSize': 5, 'from': (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d'), 'language': 'en', 'apiKey': api_key } response = requests.get(url, params=params, timeout=10) if response.status_code == 200: data = response.json() return data.get('articles', []) return [] except Exception: return [] def _get_holidays_info(self, country: str) -> str: """Retrieves national holidays via API""" try: country_code = self.country_codes.get(country, '') # If no code in our mapping, try to get it via API if not country_code: country_code = self._get_country_code_from_api(country) if not country_code: return f"🎉 **Holidays**: Country code not found for {country}" # Use Calendarific API or similar holidays_data = self._fetch_holidays_api(country_code) if not holidays_data: return f"🎉 **Holidays**: Information not available for {country}" current_month = datetime.now().month current_year = datetime.now().year result = f"🎉 **Holidays and Seasonal Events**\n" # Filter holidays from current month and upcoming months upcoming_holidays = [] for holiday in holidays_data: try: holiday_date = datetime.strptime(holiday.get('date', ''), '%Y-%m-%d') if holiday_date.month >= current_month and holiday_date.year == current_year: upcoming_holidays.append(holiday) except: continue if upcoming_holidays: result += f"**Upcoming holidays:**\n" for holiday in upcoming_holidays[:5]: name = holiday.get('name', 'Unknown holiday') date = holiday.get('date', '') result += f"• {name} ({date})\n" else: result += f"**No major holidays scheduled in the coming months**\n" return result.rstrip() except Exception: return "🎉 **Holidays**: Error during retrieval" def _fetch_holidays_api(self, country_code: str) -> list: """Récupère les fêtes via API publique""" try: # Utiliser une API publique de fêtes (exemple: Calendarific, Nager.Date) year = datetime.now().year url = f"https://date.nager.at/api/v3/PublicHolidays/{year}/{country_code}" response = requests.get(url, timeout=10) if response.status_code == 200: return response.json() return [] except Exception: return [] def _get_travel_info(self, country: str) -> str: """Retrieves travel information via REST Countries API""" try: # Use REST Countries API url = f"https://restcountries.com/v3.1/name/{country}" response = requests.get(url, timeout=10) if response.status_code == 200: data = response.json() if data: country_data = data[0] # Extract information currencies = country_data.get('currencies', {}) languages = country_data.get('languages', {}) region = country_data.get('region', 'Unknown') currency_name = list(currencies.keys())[0] if currencies else 'Unknown' language_list = list(languages.values()) if languages else ['Unknown'] result = f"✈️ **Practical Travel Information**\n" result += f"💰 Currency: {currency_name}\n" result += f"🗣️ Languages: {', '.join(language_list[:3])}\n" result += f"🌍 Region: {region}\n" result += f"📋 Check visa requirements on the country's official website" return result return f"✈️ **Travel**: Information not available for {country}" except Exception: return "✈️ **Travel**: Error during retrieval" def _get_political_info(self, country: str) -> str: """Retrieves political context via news search""" try: # Search for recent political news political_keywords = f"{country} politics government election democracy" political_data = self._search_political_news(political_keywords) if not political_data: return f"🏛️ **Politics**: Stable situation for {country}" result = f"🏛️ **Political Context**\n" # Analyze political news for article in political_data[:3]: title = article.get('title', '') if title: result += f"• {title}\n" return result.rstrip() except Exception: return "🏛️ **Politics**: Error during retrieval" def _search_political_news(self, keywords: str) -> list: """Recherche d'actualités politiques""" try: api_key = os.getenv('NEWSAPI_KEY') if api_key: url = "https://newsapi.org/v2/everything" params = { 'q': keywords, 'sortBy': 'publishedAt', 'pageSize': 5, 'from': (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d'), 'language': 'en', 'apiKey': api_key } response = requests.get(url, params=params, timeout=10) if response.status_code == 200: data = response.json() return data.get('articles', []) return [] except Exception: return [] def _get_llm_final_recommendation(self, country: str, full_report: str): """Uses Claude to generate an intelligent final recommendation""" try: if not self.claude_client: return None prompt = f"""Analyze this complete report about {country} and provide a concise final recommendation for a traveler: COMPLETE REPORT: {full_report} Your task: 1. Synthesize the most important information 2. Give a clear and actionable recommendation 3. Respond in English, maximum 200 words 4. Use a professional but accessible tone 5. If risks exist, be explicit about precautions Desired response format: 🎯 **FINAL RECOMMENDATION** [Your synthetic analysis and recommendation] If the destination is dangerous, clearly use "CHANGE DESTINATION" in your response.""" response = self.claude_client.messages.create( model="claude-3-opus-20240229", max_tokens=250, temperature=0.2, system="You are an expert travel advisor. Provide clear and practical recommendations.", messages=[ {"role": "user", "content": prompt} ] ) return response.content[0].text.strip() except Exception: return None