141 lines
6.6 KiB
Python
141 lines
6.6 KiB
Python
# 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 курсов валют.") |