# -*- coding: utf-8 -*- """ Реальные обработчики инструментов для ReAct-цикла. Каждый обработчик — асинхронная функция, принимающая параметры и возвращающая строку с результатом. Используются внешние API (поиск, погода и т.д.). """ import json import logging from typing import Dict, Any, Optional import aiohttp import asyncio from rag.config_models import AppConfig logger = logging.getLogger(__name__) class ToolHandler: """ Класс-контейнер для обработчиков инструментов. Инициализируется с конфигом (для доступа к API-ключам) и HTTP-сессией. """ def __init__(self, config: AppConfig): self.config = config self.session: Optional[aiohttp.ClientSession] = None async def _get_session(self) -> aiohttp.ClientSession: if self.session is None: self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=30)) return self.session async def close(self): if self.session: await self.session.close() self.session = None # ------------------------------------------------------------------ # 1. Поиск в интернете (используем DuckDuckGo Instant Answer API) # ------------------------------------------------------------------ async def search(self, query: str) -> str: """ Выполняет поиск в интернете через DuckDuckGo Instant Answer API. Возвращает краткую выжимку или ссылки. """ if not query or not query.strip(): return "❌ Пустой поисковый запрос." url = "https://api.duckduckgo.com/" params = { "q": query, "format": "json", "no_html": 1, "skip_disambig": 1 } try: session = await self._get_session() async with session.get(url, params=params) as resp: if resp.status != 200: return f"❌ Ошибка поиска: HTTP {resp.status}" data = await resp.json() # Извлекаем релевантную информацию results = [] if data.get("AbstractText"): results.append(f"📌 {data['AbstractText']}") if data.get("RelatedTopics"): for topic in data["RelatedTopics"][:3]: if "Text" in topic: results.append(f"• {topic['Text']}") if not results: return "😕 Ничего не найдено по вашему запросу." return "\n".join(results) except asyncio.TimeoutError: return "⏱️ Таймаут при выполнении поиска." except Exception as e: logger.exception("Ошибка в search") return f"❌ Ошибка поиска: {str(e)}" # ------------------------------------------------------------------ # 2. Погода (OpenWeatherMap) # ------------------------------------------------------------------ async def get_weather(self, city: str) -> str: """ Получает текущую погоду для города через OpenWeatherMap API. Требует API-ключ в конфиге: config.weather_api_key """ api_key = getattr(self.config, 'weather_api_key', None) if not api_key: return "❌ API-ключ для погоды не настроен (weather_api_key)." if not city or not city.strip(): return "❌ Город не указан." url = "https://api.openweathermap.org/data/2.5/weather" params = { "q": city, "appid": api_key, "units": "metric", "lang": "ru" } try: session = await self._get_session() async with session.get(url, params=params) as resp: if resp.status != 200: return f"❌ Ошибка погоды: HTTP {resp.status}" data = await resp.json() if data.get("cod") != 200: return f"❌ {data.get('message', 'Неизвестная ошибка')}" weather = data["weather"][0]["description"] temp = data["main"]["temp"] feels_like = data["main"]["feels_like"] return f"🌤️ Погода в {city}: {weather}, температура {temp:.1f}°C (ощущается {feels_like:.1f}°C)" except asyncio.TimeoutError: return "⏱️ Таймаут при запросе погоды." except Exception as e: logger.exception("Ошибка в get_weather") return f"❌ Ошибка получения погоды: {str(e)}" # ------------------------------------------------------------------ # 3. Бронирование авиабилетов (заглушка — демонстрация) # ------------------------------------------------------------------ async def book_flight(self, origin: str, destination: str, date: str) -> str: """ Заглушка для бронирования авиабилетов. В реальности здесь был бы вызов API авиакомпании или агрегатора. """ return (f"✈️ Заглушка бронирования: рейс {origin} → {destination} на {date}.\n" "В реальной системе здесь был бы вызов API бронирования.") # ------------------------------------------------------------------ # 4. Получение курса валют (заглушка) # ------------------------------------------------------------------ async def get_exchange_rate(self, from_currency: str, to_currency: str) -> str: """ Заглушка для получения курса валют. Можно использовать exchangerate-api.com или аналоги. """ return (f"💱 Заглушка курса: 1 {from_currency} = 1.0 {to_currency} (пример)\n" "В реальности здесь был бы вызов API курсов валют.")