Files
fckbot/rag/functions/tools.py
2026-06-30 23:00:48 +00:00

141 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rag/functions/tools.py
# -*- 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 курсов валют.")