128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Реестр инструментов для Telegram CLI Bot.
|
||
|
||
Инструменты - это capabilities, которые бот может использовать автономно
|
||
для выполнения задач пользователя (Agentic AI подход).
|
||
"""
|
||
|
||
import logging
|
||
from abc import ABC, abstractmethod
|
||
from typing import Dict, List, Any, Optional
|
||
from dataclasses import dataclass, field
|
||
from datetime import datetime
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@dataclass
|
||
class ToolResult:
|
||
"""Результат выполнения инструмента."""
|
||
success: bool
|
||
data: Any = None
|
||
error: Optional[str] = None
|
||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||
|
||
def to_dict(self) -> Dict:
|
||
return {
|
||
'success': self.success,
|
||
'data': self.data,
|
||
'error': self.error,
|
||
'metadata': self.metadata
|
||
}
|
||
|
||
|
||
class BaseTool(ABC):
|
||
"""Базовый класс для всех инструментов."""
|
||
|
||
name: str = "base_tool"
|
||
description: str = "Базовый инструмент"
|
||
category: str = "general"
|
||
|
||
@abstractmethod
|
||
async def execute(self, **kwargs) -> ToolResult:
|
||
"""Выполнить инструмент."""
|
||
pass
|
||
|
||
def get_capabilities(self) -> Dict:
|
||
"""Вернуть описание возможностей инструмента."""
|
||
return {
|
||
'name': self.name,
|
||
'description': self.description,
|
||
'category': self.category
|
||
}
|
||
|
||
|
||
class ToolsRegistry:
|
||
"""Реестр всех доступных инструментов."""
|
||
|
||
_instance: Optional['ToolsRegistry'] = None
|
||
_tools: Dict[str, BaseTool] = {}
|
||
|
||
def __new__(cls):
|
||
if cls._instance is None:
|
||
cls._instance = super().__new__(cls)
|
||
return cls._instance
|
||
|
||
def register(self, tool: BaseTool):
|
||
"""Зарегистрировать инструмент."""
|
||
self._tools[tool.name] = tool
|
||
logger.info(f"Зарегистрирован инструмент: {tool.name}")
|
||
|
||
def unregister(self, tool_name: str):
|
||
"""От-register инструмент."""
|
||
if tool_name in self._tools:
|
||
del self._tools[tool_name]
|
||
logger.info(f"Удален инструмент: {tool_name}")
|
||
|
||
def get(self, tool_name: str) -> Optional[BaseTool]:
|
||
"""Получить инструмент по имени."""
|
||
return self._tools.get(tool_name)
|
||
|
||
def get_all(self) -> Dict[str, BaseTool]:
|
||
"""Получить все инструменты."""
|
||
return self._tools.copy()
|
||
|
||
def get_capabilities_list(self) -> List[Dict]:
|
||
"""Получить список всех возможностей для ИИ."""
|
||
return [tool.get_capabilities() for tool in self._tools.values()]
|
||
|
||
async def execute_tool(self, tool_name: str, **kwargs) -> ToolResult:
|
||
"""Выполнить инструмент по имени."""
|
||
tool = self.get(tool_name)
|
||
if not tool:
|
||
return ToolResult(
|
||
success=False,
|
||
error=f"Инструмент '{tool_name}' не найден"
|
||
)
|
||
|
||
logger.info(f"Выполнение инструмента: {tool_name} с аргументами: {kwargs}")
|
||
try:
|
||
result = await tool.execute(**kwargs)
|
||
result.metadata['tool_name'] = tool_name
|
||
result.metadata['timestamp'] = datetime.now().isoformat()
|
||
return result
|
||
except Exception as e:
|
||
logger.exception(f"Ошибка выполнения инструмента {tool_name}: {e}")
|
||
return ToolResult(
|
||
success=False,
|
||
error=str(e),
|
||
metadata={'tool_name': tool_name}
|
||
)
|
||
|
||
|
||
# Глобальный экземпляр реестра
|
||
tools_registry = ToolsRegistry()
|
||
|
||
|
||
def register_tool(tool_class: type) -> type:
|
||
"""Декоратор для автоматической регистрации инструмента."""
|
||
tool_instance = tool_class()
|
||
tools_registry.register(tool_instance)
|
||
return tool_class
|
||
|
||
|
||
# Авто-импорт инструментов для регистрации
|
||
# Импортируем после определения register_tool чтобы декоратор сработал
|
||
from bot.tools import ddgs_tool, rss_tool, ssh_tool, cron_tool
|