CoolFace
Apppublic

CHKIM79/scalable-ai-agent-system

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
tool_manager.py614 linesDownload Raw Back to tools
1"""2Tool Management System3Handles web search, code execution, database operations, file operations, mathematical calculations4"""5import asyncio6import logging7import json8import subprocess9import tempfile10import os11import sqlite312import requests13from typing import Dict, List, Any, Optional, Callable, Union14from dataclasses import dataclass, field15from enum import Enum16import aiohttp17import math18import numpy as np19from datetime import datetime20 21 22class ToolCategory(Enum):23    WEB_SEARCH = "web_search"24    CODE_EXECUTION = "code_execution"25    DATABASE = "database"26    FILE_OPERATIONS = "file_operations"27    MATHEMATICAL = "mathematical"28    API_INTEGRATION = "api_integration"29    SYSTEM = "system"30 31 32@dataclass33class ToolDefinition:34    name: str35    category: ToolCategory36    function: Callable37    description: str38    parameters: Dict[str, Any] = field(default_factory=dict)39    timeout: int = 3040    requires_auth: bool = False41 42 43@dataclass44class ToolResult:45    tool_name: str46    success: bool47    result: Any48    execution_time: float49    error_message: Optional[str] = None50    metadata: Dict[str, Any] = field(default_factory=dict)51 52 53class WebSearchTool:54    """Web search capabilities"""55    56    def __init__(self, api_key: str = None):57        self.api_key = api_key58        self.session = None59    60    async def search(self, query: str, num_results: int = 5) -> Dict[str, Any]:61        """Perform web search"""62        if not self.session:63            self.session = aiohttp.ClientSession()64        65        # Simulate web search (replace with actual search API)66        try:67            # Using DuckDuckGo Instant Answer API as example68            url = f"https://api.duckduckgo.com/"69            params = {70                'q': query,71                'format': 'json',72                'no_html': '1',73                'skip_disambig': '1'74            }75            76            async with self.session.get(url, params=params) as response:77                data = await response.json()78                79                results = []80                if data.get('AbstractText'):81                    results.append({82                        'title': data.get('Heading', 'Search Result'),83                        'snippet': data.get('AbstractText'),84                        'url': data.get('AbstractURL', ''),85                        'source': data.get('AbstractSource', 'DuckDuckGo')86                    })87                88                # Add related topics89                for topic in data.get('RelatedTopics', [])[:num_results-1]:90                    if isinstance(topic, dict) and 'Text' in topic:91                        results.append({92                            'title': topic.get('Text', '').split(' - ')[0],93                            'snippet': topic.get('Text', ''),94                            'url': topic.get('FirstURL', ''),95                            'source': 'DuckDuckGo'96                        })97                98                return {99                    'query': query,100                    'results': results[:num_results],101                    'total_results': len(results)102                }103                104        except Exception as e:105            return {106                'query': query,107                'results': [],108                'error': str(e)109            }110 111 112class CodeExecutionTool:113    """Safe code execution in sandboxed environment"""114    115    def __init__(self):116        self.allowed_imports = {117            'math', 'json', 'datetime', 'random', 'string', 'collections',118            'itertools', 'functools', 'operator', 're', 'urllib.parse'119        }120    121    async def execute_python(self, code: str, timeout: int = 10) -> Dict[str, Any]:122        """Execute Python code safely"""123        try:124            # Create temporary file125            with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:126                # Add safety restrictions127                safe_code = f"""128import sys129import os130import subprocess131import importlib132 133# Restrict dangerous operations134def restricted_import(name, *args, **kwargs):135    allowed = {self.allowed_imports}136    if name not in allowed:137        raise ImportError(f"Import of {{name}} is not allowed")138    return original_import(name, *args, **kwargs)139 140original_import = __builtins__.__import__141__builtins__.__import__ = restricted_import142 143# Disable dangerous functions144__builtins__.open = None145__builtins__.exec = None146__builtins__.eval = None147 148# User code149{code}150"""151                f.write(safe_code)152                f.flush()153                154                # Execute with timeout155                process = await asyncio.create_subprocess_exec(156                    'python', f.name,157                    stdout=asyncio.subprocess.PIPE,158                    stderr=asyncio.subprocess.PIPE159                )160                161                try:162                    stdout, stderr = await asyncio.wait_for(163                        process.communicate(), timeout=timeout164                    )165                    166                    return {167                        'success': process.returncode == 0,168                        'stdout': stdout.decode('utf-8'),169                        'stderr': stderr.decode('utf-8'),170                        'return_code': process.returncode171                    }172                    173                except asyncio.TimeoutError:174                    process.kill()175                    return {176                        'success': False,177                        'stdout': '',178                        'stderr': 'Execution timed out',179                        'return_code': -1180                    }181                finally:182                    os.unlink(f.name)183                    184        except Exception as e:185            return {186                'success': False,187                'stdout': '',188                'stderr': str(e),189                'return_code': -1190            }191 192 193class DatabaseTool:194    """Database operations"""195    196    def __init__(self, db_path: str = ":memory:"):197        self.db_path = db_path198        self.connection = None199    200    async def connect(self):201        """Connect to database"""202        self.connection = sqlite3.connect(self.db_path)203        self.connection.row_factory = sqlite3.Row204    205    async def execute_query(self, query: str, params: tuple = None) -> Dict[str, Any]:206        """Execute SQL query safely"""207        if not self.connection:208            await self.connect()209        210        try:211            cursor = self.connection.cursor()212            213            # Basic SQL injection protection214            if any(dangerous in query.upper() for dangerous in ['DROP', 'DELETE', 'UPDATE', 'INSERT'] 215                   if not query.upper().strip().startswith(('SELECT', 'WITH'))):216                return {217                    'success': False,218                    'error': 'Only SELECT queries are allowed for safety'219                }220            221            if params:222                cursor.execute(query, params)223            else:224                cursor.execute(query)225            226            if query.upper().strip().startswith('SELECT'):227                results = [dict(row) for row in cursor.fetchall()]228                return {229                    'success': True,230                    'results': results,231                    'row_count': len(results)232                }233            else:234                self.connection.commit()235                return {236                    'success': True,237                    'affected_rows': cursor.rowcount238                }239                240        except Exception as e:241            return {242                'success': False,243                'error': str(e)244            }245 246 247class FileOperationsTool:248    """Safe file operations"""249    250    def __init__(self, base_path: str = "/tmp/agent_files"):251        self.base_path = base_path252        os.makedirs(base_path, exist_ok=True)253    254    def _safe_path(self, path: str) -> str:255        """Ensure path is within allowed directory"""256        abs_path = os.path.abspath(os.path.join(self.base_path, path))257        if not abs_path.startswith(os.path.abspath(self.base_path)):258            raise ValueError("Path outside allowed directory")259        return abs_path260    261    async def read_file(self, filename: str) -> Dict[str, Any]:262        """Read file content"""263        try:264            safe_path = self._safe_path(filename)265            with open(safe_path, 'r', encoding='utf-8') as f:266                content = f.read()267            268            return {269                'success': True,270                'content': content,271                'size': len(content)272            }273        except Exception as e:274            return {275                'success': False,276                'error': str(e)277            }278    279    async def write_file(self, filename: str, content: str) -> Dict[str, Any]:280        """Write content to file"""281        try:282            safe_path = self._safe_path(filename)283            os.makedirs(os.path.dirname(safe_path), exist_ok=True)284            285            with open(safe_path, 'w', encoding='utf-8') as f:286                f.write(content)287            288            return {289                'success': True,290                'bytes_written': len(content.encode('utf-8'))291            }292        except Exception as e:293            return {294                'success': False,295                'error': str(e)296            }297    298    async def list_files(self, directory: str = "") -> Dict[str, Any]:299        """List files in directory"""300        try:301            safe_path = self._safe_path(directory)302            files = []303            304            for item in os.listdir(safe_path):305                item_path = os.path.join(safe_path, item)306                files.append({307                    'name': item,308                    'is_directory': os.path.isdir(item_path),309                    'size': os.path.getsize(item_path) if os.path.isfile(item_path) else 0,310                    'modified': datetime.fromtimestamp(os.path.getmtime(item_path)).isoformat()311                })312            313            return {314                'success': True,315                'files': files,316                'count': len(files)317            }318        except Exception as e:319            return {320                'success': False,321                'error': str(e)322            }323 324 325class MathematicalTool:326    """Mathematical calculations and operations"""327    328    async def calculate(self, expression: str) -> Dict[str, Any]:329        """Safely evaluate mathematical expressions"""330        try:331            # Allow only safe mathematical operations332            allowed_names = {333                'abs': abs, 'round': round, 'min': min, 'max': max, 'sum': sum, 'pow': pow,334                'math': math, 'pi': math.pi, 'e': math.e,335                'sin': math.sin, 'cos': math.cos, 'tan': math.tan,336                'sqrt': math.sqrt, 'log': math.log, 'exp': math.exp337            }338            339            # Remove dangerous functions340            safe_dict = {"__builtins__": {}}341            safe_dict.update(allowed_names)342            343            result = eval(expression, safe_dict)344            345            return {346                'success': True,347                'result': result,348                'expression': expression349            }350            351        except Exception as e:352            return {353                'success': False,354                'error': str(e),355                'expression': expression356            }357    358    async def statistics(self, numbers: List[float]) -> Dict[str, Any]:359        """Calculate statistical measures"""360        try:361            if not numbers:362                return {'success': False, 'error': 'Empty list provided'}363            364            arr = np.array(numbers)365            366            return {367                'success': True,368                'count': len(numbers),369                'mean': float(np.mean(arr)),370                'median': float(np.median(arr)),371                'std_dev': float(np.std(arr)),372                'variance': float(np.var(arr)),373                'min': float(np.min(arr)),374                'max': float(np.max(arr)),375                'sum': float(np.sum(arr))376            }377            378        except Exception as e:379            return {380                'success': False,381                'error': str(e)382            }383 384 385class ToolManager:386    """Main tool management system"""387    388    def __init__(self):389        self.tools: Dict[str, ToolDefinition] = {}390        self.web_search = WebSearchTool()391        self.code_executor = CodeExecutionTool()392        self.database = DatabaseTool()393        self.file_ops = FileOperationsTool()394        self.math_tool = MathematicalTool()395        self.logger = logging.getLogger(__name__)396        397        # Register built-in tools398        self._register_builtin_tools()399    400    def _register_builtin_tools(self):401        """Register all built-in tools"""402        403        # Web search tools404        self.register_tool(405            "web_search",406            self.web_search.search,407            "Search the web for information",408            ToolCategory.WEB_SEARCH,409            {"query": str, "num_results": int}410        )411        412        # Code execution tools413        self.register_tool(414            "execute_python",415            self.code_executor.execute_python,416            "Execute Python code safely",417            ToolCategory.CODE_EXECUTION,418            {"code": str, "timeout": int}419        )420        421        # Database tools422        self.register_tool(423            "db_query",424            self.database.execute_query,425            "Execute SQL query",426            ToolCategory.DATABASE,427            {"query": str, "params": tuple}428        )429        430        # File operation tools431        self.register_tool(432            "read_file",433            self.file_ops.read_file,434            "Read file content",435            ToolCategory.FILE_OPERATIONS,436            {"filename": str}437        )438        439        self.register_tool(440            "write_file",441            self.file_ops.write_file,442            "Write content to file",443            ToolCategory.FILE_OPERATIONS,444            {"filename": str, "content": str}445        )446        447        self.register_tool(448            "list_files",449            self.file_ops.list_files,450            "List files in directory",451            ToolCategory.FILE_OPERATIONS,452            {"directory": str}453        )454        455        # Mathematical tools456        self.register_tool(457            "calculate",458            self.math_tool.calculate,459            "Perform mathematical calculations",460            ToolCategory.MATHEMATICAL,461            {"expression": str}462        )463        464        self.register_tool(465            "statistics",466            self.math_tool.statistics,467            "Calculate statistical measures",468            ToolCategory.MATHEMATICAL,469            {"numbers": List[float]}470        )471    472    def register_tool(self, 473                     name: str, 474                     function: Callable, 475                     description: str,476                     category: ToolCategory = ToolCategory.SYSTEM,477                     parameters: Dict[str, Any] = None,478                     timeout: int = 30,479                     requires_auth: bool = False):480        """Register a new tool"""481        482        tool_def = ToolDefinition(483            name=name,484            category=category,485            function=function,486            description=description,487            parameters=parameters or {},488            timeout=timeout,489            requires_auth=requires_auth490        )491        492        self.tools[name] = tool_def493        self.logger.info(f"Registered tool: {name}")494    495    async def execute_tool(self, tool_name: str, parameters: Dict[str, Any] = None, **kwargs) -> ToolResult:496        """Execute a tool with given parameters"""497        start_time = asyncio.get_event_loop().time()498        499        if tool_name not in self.tools:500            return ToolResult(501                tool_name=tool_name,502                success=False,503                result=None,504                execution_time=0,505                error_message=f"Tool '{tool_name}' not found"506            )507        508        tool_def = self.tools[tool_name]509        510        try:511            # Merge parameters and kwargs512            all_params = {}513            if parameters:514                all_params.update(parameters)515            all_params.update(kwargs)516            517            # Execute with timeout518            result = await asyncio.wait_for(519                tool_def.function(**all_params),520                timeout=tool_def.timeout521            )522            523            execution_time = asyncio.get_event_loop().time() - start_time524            525            return ToolResult(526                tool_name=tool_name,527                success=True,528                result=result,529                execution_time=execution_time530            )531            532        except asyncio.TimeoutError:533            return ToolResult(534                tool_name=tool_name,535                success=False,536                result=None,537                execution_time=tool_def.timeout,538                error_message=f"Tool execution timed out after {tool_def.timeout}s"539            )540            541        except Exception as e:542            execution_time = asyncio.get_event_loop().time() - start_time543            return ToolResult(544                tool_name=tool_name,545                success=False,546                result=None,547                execution_time=execution_time,548                error_message=str(e)549            )550    551    def get_available_tools(self, category: ToolCategory = None) -> List[Dict[str, Any]]:552        """Get list of available tools"""553        tools_list = []554        555        for name, tool_def in self.tools.items():556            if category is None or tool_def.category == category:557                tools_list.append({558                    'name': name,559                    'category': tool_def.category.value,560                    'description': tool_def.description,561                    'parameters': tool_def.parameters,562                    'timeout': tool_def.timeout563                })564        565        return tools_list566    567    def get_tool_schema(self, tool_name: str) -> Optional[Dict[str, Any]]:568        """Get OpenAI Functions compatible schema for a tool"""569        if tool_name not in self.tools:570            return None571        572        tool_def = self.tools[tool_name]573        574        # Convert to OpenAI Functions format575        properties = {}576        required = []577        578        for param_name, param_type in tool_def.parameters.items():579            if param_type == str:580                properties[param_name] = {"type": "string"}581            elif param_type == int:582                properties[param_name] = {"type": "integer"}583            elif param_type == float:584                properties[param_name] = {"type": "number"}585            elif param_type == bool:586                properties[param_name] = {"type": "boolean"}587            elif param_type == list:588                properties[param_name] = {"type": "array"}589            590            required.append(param_name)591        592        return {593            "name": tool_name,594            "description": tool_def.description,595            "parameters": {596                "type": "object",597                "properties": properties,598                "required": required599            }600        }601    602    async def initialize(self):603        """Initialize tool manager"""604        await self.database.connect()605        self.logger.info("Tool manager initialized")606    607    async def shutdown(self):608        """Shutdown tool manager"""609        if self.web_search.session:610            await self.web_search.session.close()611        if self.database.connection:612            self.database.connection.close()613        self.logger.info("Tool manager shutdown")614