diff --git a/README.md b/README.md index e5be1ea..e8621ba 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Un agente configurabile basato su DeepAgents che supporta più provider LLM. - **Configurazione semplice**: Imposta le variabili d'ambiente per scegliere il tuo provider e modello - **Configurazione persistente**: Gestisci e conserva le configurazioni tramite CLI o TUI - **Estensibile**: Aggiungi strumenti personalizzati, modifica i prompt e configura i sub-agenti secondo le tue esigenze +- **Strumenti unificati**: Gestisci qualsiasi provider con la stessa interfaccia - **Strumenti personalizzati**: Include strumenti per gestire Proxmox e Ollama ## Installazione @@ -117,9 +118,61 @@ python main.py Puoi quindi interagire con l'agente nel terminale. Digita 'exit' per uscire. +## Strumenti Unificati per Provider + +MaxClaw Agent ora fornisce un'interfaccia unificata per gestire qualsiasi provider LLM. Una volta configurato un provider, puoi usare gli stessi comandi per tutti. + +### Strumenti Disponibili per Ogni Provider + +Per ogni provider configurato (Ollama, OpenAI, OpenRouter), hai accesso a: + +- `{provider}_list_models`: Elenca tutti i modelli disponibili +- `{provider}_model_info`: Ottieni informazioni dettagliate su un modello +- `{provider}_test_connection`: Testa la connessione al provider +- `{provider}_get_default_model`: Mostra il modello predefinito +- `{provider}_set_default_model`: Imposta un modello predefinito +- `{provider}_info`: Mostra informazioni sulla configurazione del provider + +### Esempi di Utilizzo + +```bash +# Con Ollama configurato: +"List available Ollama models" +"Test connection to Ollama" +"What's the default model for Ollama?" + +# Con OpenAI configurato: +"List available OpenAI models" +"Test connection to OpenAI" +"Show me OpenAI configuration" + +# Con OpenRouter configurato: +"List available OpenRouter models" +"Test connection to OpenRouter" +"Show me OpenRouter configuration" +``` + +### Configurazione Automatica + +Gli strumenti vengono caricati automaticamente in base alle variabili d'ambiente configurate: + +```bash +# Se hai configurato Ollama: +export OLLAMA_BASE_URL="http://192.168.0.50:11434" +# → Verranno caricati automaticamente gli strumenti Ollama + +# Se hai configurato OpenAI: +export OPENAI_API_KEY="sk-..." +# → Verranno caricati automaticamente gli strumenti OpenAI + +# Se hai configurato OpenRouter: +export OPENROUTER_API_KEY="sk-or-..." +# → Verranno caricati automaticamente gli strumenti OpenRouter +``` + ## Strumenti Personalizzati -MaxClaw Agent include strumenti personalizzati per gestire il tuo ambiente: +MaxClaw Agent include anche strumenti personalizzati per gestire il tuo ambiente: ### Strumenti Proxmox Configura le variabili d'ambiente per abilitare gli strumenti Proxmox: @@ -139,10 +192,8 @@ Strumenti disponibili: - `proxmox_create_snapshot`: Crea uno snapshot - `proxmox_list_snapshots`: Elenca gli snapshot di una VM -### Strumenti Ollama -Gli strumenti Ollama sono sempre disponibili e utilizzano l'URL configurato in `OLLAMA_BASE_URL`. - -Strumenti disponibili: +### Strumenti Ollama (Legacy) +Gli strumenti Ollama specifici sono ancora disponibili per retrocompatibilità: - `ollama_list_models`: Elenca tutti i modelli disponibili - `ollama_model_info`: Ottieni informazioni dettagliate su un modello - `ollama_pull_model`: Scarica un nuovo modello diff --git a/main.py b/main.py index b63883c..70691f6 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ """ MaxClaw Agent - A configurable agent harness using DeepAgents Supports multiple LLM providers via LangChain's init_chat_model -Enhanced with custom tools for Proxmox and Ollama management +Enhanced with unified provider tools and custom tools """ import os import sys @@ -14,7 +14,8 @@ from langchain.tools import Tool # Import custom tools try: - from tools import get_proxmox_tools, get_ollama_tools + from tools import get_proxmox_tools + from tools.provider_tools import get_provider_tools, get_all_provider_tools TOOLS_AVAILABLE = True except ImportError: TOOLS_AVAILABLE = False @@ -94,14 +95,13 @@ def get_custom_tools() -> List[Tool]: except Exception as e: print(f"⚠️ Could not load Proxmox tools: {e}") - # Add Ollama tools if configured - ollama_url = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434") + # Add unified provider tools for all configured providers try: - ollama_tools = get_ollama_tools(base_url=ollama_url) - custom_tools.extend(ollama_tools) - print(f"✅ Added {len(ollama_tools)} Ollama tools") + provider_tools = get_all_provider_tools() + custom_tools.extend(provider_tools) + print(f"✅ Added {len(provider_tools)} provider tools") except Exception as e: - print(f"⚠️ Could not load Ollama tools: {e}") + print(f"⚠️ Could not load provider tools: {e}") return custom_tools @@ -115,8 +115,15 @@ def create_maxclaw_agent(system_prompt: Optional[str] = None): manage context, and delegate to subagents. You are designed to be configurable to work with various LLM providers. - You have access to custom tools for managing Proxmox virtual machines and Ollama models. - Use these tools when asked about system administration, virtualization, or AI model management.""" + You have access to: + 1. Unified provider tools for managing LLM providers (list models, test connections, etc.) + 2. Custom tools for managing Proxmox virtual machines + 3. Standard DeepAgents tools (planning, filesystem, shell, subagents) + + Use these tools when asked about: + - LLM provider management (listing models, testing connections, setting defaults) + - System administration and virtualization + - AI model management and configuration""" model = load_model_from_config() @@ -143,9 +150,18 @@ def main(): agent = create_maxclaw_agent() print("✅ Agent initialized successfully!") - # Simple test interaction + # Show available tools + print("\n🔧 Available tools:") + print(" • Provider tools: list models, test connections, set defaults") + print(" • Proxmox tools: manage VMs, snapshots, resources") + print(" • Standard tools: planning, filesystem, shell, subagents") + print("\n💬 You can now interact with the agent. Type 'exit' to quit.") - print("💡 Try asking about Proxmox VMs or Ollama models!") + print("💡 Try asking:") + print(" - 'List available models for my provider'") + print(" - 'Test connection to Ollama'") + print(" - 'Show me Proxmox VMs'") + print(" - 'What's the default model for OpenAI?'") while True: user_input = input("\n👤 You: ").strip() diff --git a/tools/__init__.py b/tools/__init__.py index ca684e9..3e4225a 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -1,14 +1,33 @@ """ MaxClaw Agent Tools Package -Provides custom tools for Proxmox and Ollama management +Provides custom tools for Proxmox, Ollama, and unified provider management """ from .proxmox_tools import get_proxmox_tools, ProxmoxTools from .ollama_tools import get_ollama_tools, OllamaTools +from .provider_tools import ( + get_provider_tools, + get_all_provider_tools, + BaseProviderTools, + OllamaProviderTools, + OpenAIProviderTools, + OpenRouterProviderTools +) __all__ = [ + # Proxmox tools 'get_proxmox_tools', 'ProxmoxTools', + + # Ollama tools (legacy) 'get_ollama_tools', - 'OllamaTools' + 'OllamaTools', + + # Unified provider tools + 'get_provider_tools', + 'get_all_provider_tools', + 'BaseProviderTools', + 'OllamaProviderTools', + 'OpenAIProviderTools', + 'OpenRouterProviderTools' ] \ No newline at end of file diff --git a/tools/provider_tools.py b/tools/provider_tools.py new file mode 100644 index 0000000..86ef01e --- /dev/null +++ b/tools/provider_tools.py @@ -0,0 +1,630 @@ +""" +Unified Provider Tools for MaxClaw Agent +Provides a consistent interface for managing different LLM providers +""" +import os +import json +import requests +from abc import ABC, abstractmethod +from typing import Dict, List, Optional, Any, Tuple +from langchain.tools import Tool + + +class BaseProviderTools(ABC): + """Base class for provider-specific tools""" + + def __init__(self, provider_name: str, **kwargs): + """ + Initialize provider tools + + Args: + provider_name: Name of the provider (e.g., "ollama", "openai", "openrouter") + **kwargs: Provider-specific configuration + """ + self.provider_name = provider_name + self.config = kwargs + + @abstractmethod + def list_models(self) -> str: + """List available models for this provider""" + pass + + @abstractmethod + def get_model_info(self, model_name: str) -> str: + """Get detailed information about a specific model""" + pass + + @abstractmethod + def test_connection(self) -> str: + """Test connection to the provider""" + pass + + @abstractmethod + def get_default_model(self) -> str: + """Get the default model for this provider""" + pass + + @abstractmethod + def set_default_model(self, model_name: str) -> str: + """Set the default model for this provider""" + pass + + @abstractmethod + def get_provider_info(self) -> str: + """Get information about the provider configuration""" + pass + + def get_tools(self) -> List[Tool]: + """ + Get a list of LangChain tools for this provider + + Returns: + List of tools + """ + return [ + Tool( + name=f"{self.provider_name}_list_models", + func=self.list_models, + description=f"List all available models for {self.provider_name}" + ), + Tool( + name=f"{self.provider_name}_model_info", + func=self.get_model_info, + description=f"Get detailed information about a specific {self.provider_name} model" + ), + Tool( + name=f"{self.provider_name}_test_connection", + func=self.test_connection, + description=f"Test connection to {self.provider_name} provider" + ), + Tool( + name=f"{self.provider_name}_get_default_model", + func=self.get_default_model, + description=f"Get the default model for {self.provider_name}" + ), + Tool( + name=f"{self.provider_name}_set_default_model", + func=self.set_default_model, + description=f"Set the default model for {self.provider_name}" + ), + Tool( + name=f"{self.provider_name}_info", + func=self.get_provider_info, + description=f"Get information about {self.provider_name} configuration" + ) + ] + + +class OllamaProviderTools(BaseProviderTools): + """Ollama provider tools implementation""" + + def __init__(self, base_url: str = "http://localhost:11434", **kwargs): + """ + Initialize Ollama tools + + Args: + base_url: Base URL of the Ollama instance + **kwargs: Additional configuration + """ + super().__init__("ollama", base_url=base_url, **kwargs) + self.base_url = base_url.rstrip('/') + self.api_url = f"{self.base_url}/api" + + def _make_request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: + """Make a request to the Ollama API""" + try: + url = f"{self.api_url}/{endpoint}" + + if method.upper() == "GET": + response = requests.get(url, timeout=10) + elif method.upper() == "POST": + response = requests.post(url, json=data, timeout=30) + else: + return {"success": False, "error": f"Unsupported method: {method}"} + + if response.status_code == 200: + try: + return {"success": True, "data": response.json()} + except json.JSONDecodeError: + return {"success": True, "data": response.text} + else: + return {"success": False, "error": f"HTTP {response.status_code}: {response.text}"} + + except requests.exceptions.Timeout: + return {"success": False, "error": "Request timed out"} + except requests.exceptions.ConnectionError: + return {"success": False, "error": f"Connection failed to {self.base_url}"} + except Exception as e: + return {"success": False, "error": str(e)} + + def list_models(self) -> str: + """List all available models on the Ollama instance""" + result = self._make_request("tags") + + if not result["success"]: + return f"Error listing models: {result['error']}" + + models = result["data"].get("models", []) + if not models: + return "No models found on the Ollama instance." + + output = f"Available Ollama Models ({self.base_url}):\n" + output += "=" * 50 + "\n" + + for model in models: + name = model.get("name", "Unknown") + size = model.get("size", 0) + + # Convert size to human readable format + if size > 1024 * 1024 * 1024: + size_str = f"{size / (1024 * 1024 * 1024):.1f} GB" + elif size > 1024 * 1024: + size_str = f"{size / (1024 * 1024):.1f} MB" + else: + size_str = f"{size / 1024:.1f} KB" + + output += f"• {name} ({size_str})\n" + + return output + + def get_model_info(self, model_name: str) -> str: + """Get detailed information about a specific model""" + result = self._make_request("show", method="POST", data={"name": model_name}) + + if not result["success"]: + return f"Error getting model info: {result['error']}" + + model_info = result["data"] + + output = f"Model Information: {model_name}\n" + output += "=" * 50 + "\n" + + if "license" in model_info: + output += f"License: {model_info['license']}\n" + + if "parameters" in model_info: + output += f"Parameters: {model_info['parameters']}\n" + + if "details" in model_info: + details = model_info["details"] + if "parameter_size" in details: + output += f"Parameter Size: {details['parameter_size']}\n" + if "quantization_level" in details: + output += f"Quantization Level: {details['quantization_level']}\n" + + return output + + def test_connection(self) -> str: + """Test connection to Ollama""" + try: + response = requests.get(f"{self.base_url}/", timeout=5) + if response.status_code == 200: + return f"✅ Ollama instance at {self.base_url} is running and responsive." + else: + return f"⚠️ Ollama instance at {self.base_url} returned status {response.status_code}." + except requests.exceptions.ConnectionError: + return f"❌ Cannot connect to Ollama instance at {self.base_url}." + except requests.exceptions.Timeout: + return f"❌ Connection to Ollama instance at {self.base_url} timed out." + except Exception as e: + return f"❌ Error checking Ollama status: {str(e)}" + + def get_default_model(self) -> str: + """Get the default model for Ollama""" + # Ollama doesn't have a built-in default model concept + # We'll return the first available model or a message + result = self._make_request("tags") + + if not result["success"]: + return f"Error getting models: {result['error']}" + + models = result["data"].get("models", []) + if not models: + return "No models available on Ollama instance." + + # Return the first model as default + first_model = models[0].get("name", "Unknown") + return f"Default model (first available): {first_model}" + + def set_default_model(self, model_name: str) -> str: + """Set the default model for Ollama""" + # Ollama doesn't have a built-in default model concept + # We'll just verify the model exists + result = self._make_request("show", method="POST", data={"name": model_name}) + + if result["success"]: + return f"✅ Model '{model_name}' exists and can be used as default." + else: + return f"❌ Model '{model_name}' not found on Ollama instance." + + def get_provider_info(self) -> str: + """Get information about Ollama configuration""" + output = "Ollama Provider Configuration:\n" + output += "=" * 50 + "\n" + output += f"Base URL: {self.base_url}\n" + output += f"API URL: {self.api_url}\n" + + # Test connection + try: + response = requests.get(f"{self.base_url}/", timeout=5) + if response.status_code == 200: + output += "Status: ✅ Connected\n" + else: + output += f"Status: ⚠️ HTTP {response.status_code}\n" + except: + output += "Status: ❌ Not connected\n" + + # Get model count + result = self._make_request("tags") + if result["success"]: + models = result["data"].get("models", []) + output += f"Available Models: {len(models)}\n" + else: + output += "Available Models: Unknown\n" + + return output + + +class OpenAIProviderTools(BaseProviderTools): + """OpenAI provider tools implementation""" + + def __init__(self, api_key: str, **kwargs): + """ + Initialize OpenAI tools + + Args: + api_key: OpenAI API key + **kwargs: Additional configuration + """ + super().__init__("openai", api_key=api_key, **kwargs) + self.api_key = api_key + self.api_url = "https://api.openai.com/v1" + + def _make_request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: + """Make a request to the OpenAI API""" + try: + url = f"{self.api_url}/{endpoint}" + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + if method.upper() == "GET": + response = requests.get(url, headers=headers, timeout=10) + elif method.upper() == "POST": + response = requests.post(url, headers=headers, json=data, timeout=30) + else: + return {"success": False, "error": f"Unsupported method: {method}"} + + if response.status_code == 200: + try: + return {"success": True, "data": response.json()} + except json.JSONDecodeError: + return {"success": True, "data": response.text} + else: + return {"success": False, "error": f"HTTP {response.status_code}: {response.text}"} + + except requests.exceptions.Timeout: + return {"success": False, "error": "Request timed out"} + except requests.exceptions.ConnectionError: + return {"success": False, "error": "Connection failed to OpenAI API"} + except Exception as e: + return {"success": False, "error": str(e)} + + def list_models(self) -> str: + """List all available models on OpenAI""" + result = self._make_request("models") + + if not result["success"]: + return f"Error listing models: {result['error']}" + + models = result["data"].get("data", []) + if not models: + return "No models found on OpenAI." + + # Filter to show only common models + common_models = ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo", "gpt-4", "gpt-3.5-turbo", "text-embedding-ada-002"] + + output = "Available OpenAI Models:\n" + output += "=" * 50 + "\n" + + for model in models: + model_id = model.get("id", "") + if model_id in common_models or "gpt" in model_id or "text-embedding" in model_id: + output += f"• {model_id}\n" + + return output + + def get_model_info(self, model_name: str) -> str: + """Get detailed information about a specific model""" + result = self._make_request(f"models/{model_name}") + + if not result["success"]: + return f"Error getting model info: {result['error']}" + + model_info = result["data"] + + output = f"Model Information: {model_name}\n" + output += "=" * 50 + "\n" + output += f"ID: {model_info.get('id', 'Unknown')}\n" + output += f"Object: {model_info.get('object', 'Unknown')}\n" + output += f"Created: {model_info.get('created', 'Unknown')}\n" + output += f"Owned by: {model_info.get('owned_by', 'Unknown')}\n" + + return output + + def test_connection(self) -> str: + """Test connection to OpenAI""" + try: + # Try to list models as a connection test + result = self._make_request("models") + if result["success"]: + return "✅ OpenAI API connection successful." + else: + return f"❌ OpenAI API connection failed: {result['error']}" + except Exception as e: + return f"❌ Error connecting to OpenAI: {str(e)}" + + def get_default_model(self) -> str: + """Get the default model for OpenAI""" + # OpenAI doesn't have a built-in default model concept + # Return a common default + return "Default model: gpt-4o (recommended for most tasks)" + + def set_default_model(self, model_name: str) -> str: + """Set the default model for OpenAI""" + # OpenAI doesn't have a built-in default model concept + # Just verify the model exists + result = self._make_request(f"models/{model_name}") + + if result["success"]: + return f"✅ Model '{model_name}' exists and can be used as default." + else: + return f"❌ Model '{model_name}' not found on OpenAI." + + def get_provider_info(self) -> str: + """Get information about OpenAI configuration""" + output = "OpenAI Provider Configuration:\n" + output += "=" * 50 + "\n" + output += f"API URL: {self.api_url}\n" + output += f"API Key: {'Set' if self.api_key else 'Not set'}\n" + + # Test connection + try: + result = self._make_request("models") + if result["success"]: + output += "Status: ✅ Connected\n" + else: + output += f"Status: ❌ Connection failed\n" + except: + output += "Status: ❌ Not connected\n" + + return output + + +class OpenRouterProviderTools(BaseProviderTools): + """OpenRouter provider tools implementation""" + + def __init__(self, api_key: str, base_url: str = "https://openrouter.ai/api/v1", **kwargs): + """ + Initialize OpenRouter tools + + Args: + api_key: OpenRouter API key + base_url: Base URL for OpenRouter API + **kwargs: Additional configuration + """ + super().__init__("openrouter", api_key=api_key, base_url=base_url, **kwargs) + self.api_key = api_key + self.base_url = base_url.rstrip('/') + self.api_url = f"{self.base_url}" + + def _make_request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: + """Make a request to the OpenRouter API""" + try: + url = f"{self.api_url}/{endpoint}" + headers = { + "Authorization": f"Bearer {self.api_key}", + "Content-Type": "application/json" + } + + if method.upper() == "GET": + response = requests.get(url, headers=headers, timeout=10) + elif method.upper() == "POST": + response = requests.post(url, headers=headers, json=data, timeout=30) + else: + return {"success": False, "error": f"Unsupported method: {method}"} + + if response.status_code == 200: + try: + return {"success": True, "data": response.json()} + except json.JSONDecodeError: + return {"success": True, "data": response.text} + else: + return {"success": False, "error": f"HTTP {response.status_code}: {response.text}"} + + except requests.exceptions.Timeout: + return {"success": False, "error": "Request timed out"} + except requests.exceptions.ConnectionError: + return {"success": False, "error": f"Connection failed to {self.base_url}"} + except Exception as e: + return {"success": False, "error": str(e)} + + def list_models(self) -> str: + """List all available models on OpenRouter""" + result = self._make_request("models") + + if not result["success"]: + return f"Error listing models: {result['error']}" + + models = result["data"].get("data", []) + if not models: + return "No models found on OpenRouter." + + output = "Available OpenRouter Models:\n" + output += "=" * 50 + "\n" + + for model in models[:20]: # Limit to first 20 models + model_id = model.get("id", "") + name = model.get("name", "") + description = model.get("description", "") + + output += f"• {model_id}\n" + if name: + output += f" Name: {name}\n" + if description: + output += f" Description: {description[:100]}...\n" + output += "\n" + + return output + + def get_model_info(self, model_name: str) -> str: + """Get detailed information about a specific model""" + result = self._make_request(f"models/{model_name}") + + if not result["success"]: + return f"Error getting model info: {result['error']}" + + model_info = result["data"] + + output = f"Model Information: {model_name}\n" + output += "=" * 50 + "\n" + output += f"ID: {model_info.get('id', 'Unknown')}\n" + output += f"Name: {model_info.get('name', 'Unknown')}\n" + output += f"Description: {model_info.get('description', 'Unknown')}\n" + + if "pricing" in model_info: + pricing = model_info["pricing"] + output += f"Pricing: {pricing}\n" + + return output + + def test_connection(self) -> str: + """Test connection to OpenRouter""" + try: + # Try to list models as a connection test + result = self._make_request("models") + if result["success"]: + return "✅ OpenRouter API connection successful." + else: + return f"❌ OpenRouter API connection failed: {result['error']}" + except Exception as e: + return f"❌ Error connecting to OpenRouter: {str(e)}" + + def get_default_model(self) -> str: + """Get the default model for OpenRouter""" + # OpenRouter doesn't have a built-in default model concept + # Return a common default + return "Default model: anthropic/claude-3.5-sonnet (recommended for most tasks)" + + def set_default_model(self, model_name: str) -> str: + """Set the default model for OpenRouter""" + # OpenRouter doesn't have a built-in default model concept + # Just verify the model exists + result = self._make_request(f"models/{model_name}") + + if result["success"]: + return f"✅ Model '{model_name}' exists and can be used as default." + else: + return f"❌ Model '{model_name}' not found on OpenRouter." + + def get_provider_info(self) -> str: + """Get information about OpenRouter configuration""" + output = "OpenRouter Provider Configuration:\n" + output += "=" * 50 + "\n" + output += f"API URL: {self.api_url}\n" + output += f"API Key: {'Set' if self.api_key else 'Not set'}\n" + + # Test connection + try: + result = self._make_request("models") + if result["success"]: + output += "Status: ✅ Connected\n" + else: + output += f"Status: ❌ Connection failed\n" + except: + output += "Status: ❌ Not connected\n" + + return output + + +def get_provider_tools(provider: str, **kwargs) -> List[Tool]: + """ + Get tools for a specific provider + + Args: + provider: Provider name ("ollama", "openai", "openrouter") + **kwargs: Provider-specific configuration + + Returns: + List of tools for the provider + """ + provider = provider.lower() + + if provider == "ollama": + base_url = kwargs.get("base_url", os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")) + tools = OllamaProviderTools(base_url=base_url) + return tools.get_tools() + + elif provider == "openai": + api_key = kwargs.get("api_key", os.getenv("OPENAI_API_KEY")) + if not api_key: + print("Warning: OpenAI API key not provided") + return [] + tools = OpenAIProviderTools(api_key=api_key) + return tools.get_tools() + + elif provider == "openrouter": + api_key = kwargs.get("api_key", os.getenv("OPENROUTER_API_KEY")) + base_url = kwargs.get("base_url", os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1")) + if not api_key: + print("Warning: OpenRouter API key not provided") + return [] + tools = OpenRouterProviderTools(api_key=api_key, base_url=base_url) + return tools.get_tools() + + else: + print(f"Warning: Unknown provider '{provider}'") + return [] + + +def get_all_provider_tools() -> List[Tool]: + """ + Get tools for all configured providers + + Returns: + List of tools for all configured providers + """ + all_tools = [] + + # Check for Ollama configuration + ollama_url = os.getenv("OLLAMA_BASE_URL") + if ollama_url: + try: + ollama_tools = get_provider_tools("ollama", base_url=ollama_url) + all_tools.extend(ollama_tools) + print(f"✅ Added {len(ollama_tools)} Ollama tools") + except Exception as e: + print(f"⚠️ Could not load Ollama tools: {e}") + + # Check for OpenAI configuration + openai_key = os.getenv("OPENAI_API_KEY") + if openai_key: + try: + openai_tools = get_provider_tools("openai", api_key=openai_key) + all_tools.extend(openai_tools) + print(f"✅ Added {len(openai_tools)} OpenAI tools") + except Exception as e: + print(f"⚠️ Could not load OpenAI tools: {e}") + + # Check for OpenRouter configuration + openrouter_key = os.getenv("OPENROUTER_API_KEY") + if openrouter_key: + try: + openrouter_tools = get_provider_tools("openrouter", api_key=openrouter_key) + all_tools.extend(openrouter_tools) + print(f"✅ Added {len(openrouter_tools)} OpenRouter tools") + except Exception as e: + print(f"⚠️ Could not load OpenRouter tools: {e}") + + return all_tools \ No newline at end of file