diff --git a/README.md b/README.md index 4fd0ca6..393c3a5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Un agente configurabile basato su DeepAgents che supporta più provider LLM. ## Caratteristiche -- **Indipendente dal provider**: Funziona con OpenAI, Ollama, OpenRouter e qualsiasi altro provider supportato da `init_chat_model` di LangChain +- **Indipendente dal provider**: Funziona con OpenAI, Ollama, OpenRouter, Anthropic, Google e qualsiasi altro provider supportato da `init_chat_model` di LangChain - **Basato su DeepAgents**: Include pianificazione, accesso al filesystem, sub-agenti e gestione del contesto out of the box - **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 @@ -17,6 +17,7 @@ Un agente configurabile basato su DeepAgents che supporta più provider LLM. - **API REST**: Server API per accesso remoto - **Web UI**: Interfaccia web per gestione configurazioni - **Comandi integrati**: Comandi speciali con prefisso "/" per gestione rapida +- **Monitoraggio**: Sistema di logging e monitoraggio delle performance ## Installazione @@ -68,12 +69,14 @@ Puoi configurare MaxClaw Agent in diversi modi: Imposta le seguenti variabili d'ambiente per configurare il tuo provider LLM: -- `MODEL_PROVIDER`: Il provider da utilizzare (es. "openai", "ollama", "openrouter") -- `MODEL_NAME`: Il nome specifico del modello (es. "gpt-4o", "gemma2:9b") +- `MODEL_PROVIDER`: Il provider da utilizzare (es. "openai", "ollama", "openrouter", "anthropic", "google") +- `MODEL_NAME`: Il nome specifico del modello (es. "gpt-4o", "gemma2:9b", "claude-3-5-sonnet") - Variabili specifiche per il provider: - Per OpenAI: `OPENAI_API_KEY` - Per Ollama: `OLLAMA_BASE_URL` (predefinito a http://localhost:11434) - Per OpenRouter: `OPENROUTER_API_KEY` e opzionalmente `OPENROUTER_BASE_URL` (predefinito a https://openrouter.ai/api/v1) + - Per Anthropic: `ANTHROPIC_API_KEY` + - Per Google: `GOOGLE_API_KEY` ### Esempi di Configurazione @@ -98,6 +101,20 @@ export MODEL_NAME="anthropic/claude-3.5-sonnet" export OPENROUTER_API_KEY="la-tua-chiave-api-openrouter" ``` +#### Anthropic +```bash +export MODEL_PROVIDER="anthropic" +export MODEL_NAME="claude-3-5-sonnet-20241022" +export ANTHROPIC_API_KEY="la-tua-chiave-api-anthropic" +``` + +#### Google +```bash +export MODEL_PROVIDER="google" +export MODEL_NAME="gemini-1.5-pro" +export GOOGLE_API_KEY="la-tua-chiave-api-google" +``` + ### 2. Configurazione Persistente (Consigliato) Utilizza la CLI, la TUI o l'interfaccia web per gestire le configurazioni che persistono tra le sessioni. @@ -277,9 +294,19 @@ curl -X POST http://localhost:8000/configurations \ MaxClaw Agent ora fornisce un'interfaccia unificata per gestire qualsiasi provider LLM. Una volta configurato un provider, puoi usare gli stessi comandi per tutti. +### Provider Supportati + +MaxClaw Agent supporta i seguenti provider: + +1. **Ollama** - Modelli locali +2. **OpenAI** - GPT-4o, GPT-4o-mini, GPT-4-turbo, ecc. +3. **OpenRouter** - Accesso a centinaia di modelli +4. **Anthropic** - Claude 3.5 Sonnet, Claude 3 Opus, Claude 3 Haiku, ecc. +5. **Google** - Gemini 1.5 Pro, Gemini 1.5 Flash, ecc. + ### Strumenti Disponibili per Ogni Provider -Per ogni provider configurato (Ollama, OpenAI, OpenRouter), hai accesso a: +Per ogni provider configurato, hai accesso a: - `{provider}_list_models`: Elenca tutti i modelli disponibili - `{provider}_model_info`: Ottieni informazioni dettagliate su un modello @@ -305,6 +332,16 @@ Per ogni provider configurato (Ollama, OpenAI, OpenRouter), hai accesso a: "List available OpenRouter models" "Test connection to OpenRouter" "Show me OpenRouter configuration" + +# Con Anthropic configurato: +"List available Anthropic models" +"Test connection to Anthropic" +"Show me Anthropic configuration" + +# Con Google configurato: +"List available Google models" +"Test connection to Google" +"Show me Google configuration" ``` ### Configurazione Automatica @@ -323,6 +360,14 @@ export OPENAI_API_KEY="sk-..." # Se hai configurato OpenRouter: export OPENROUTER_API_KEY="sk-or-..." # → Verranno caricati automaticamente gli strumenti OpenRouter + +# Se hai configurato Anthropic: +export ANTHROPIC_API_KEY="sk-ant-..." +# → Verranno caricati automaticamente gli strumenti Anthropic + +# Se hai configurato Google: +export GOOGLE_API_KEY="AIza..." +# → Verranno caricati automaticamente gli strumenti Google ``` ## Strumenti Personalizzati @@ -402,6 +447,8 @@ docker run -it --rm \ -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \ -e OPENAI_API_KEY=sk-... \ -e OPENROUTER_API_KEY=sk-or-... \ + -e ANTHROPIC_API_KEY=sk-ant-... \ + -e GOOGLE_API_KEY=AIza... \ -e PROXMOX_HOST=192.168.0.100 \ -e PROXMOX_USER=root \ -e PROXMOX_PASSWORD=password \ diff --git a/tools/provider_tools.py b/tools/provider_tools.py index 86ef01e..dc5d7ac 100644 --- a/tools/provider_tools.py +++ b/tools/provider_tools.py @@ -548,12 +548,337 @@ class OpenRouterProviderTools(BaseProviderTools): return output +class AnthropicProviderTools(BaseProviderTools): + """Anthropic provider tools implementation""" + + def __init__(self, api_key: str, **kwargs): + """ + Initialize Anthropic tools + + Args: + api_key: Anthropic API key + **kwargs: Additional configuration + """ + super().__init__("anthropic", api_key=api_key, **kwargs) + self.api_key = api_key + self.api_url = "https://api.anthropic.com/v1" + + def _make_request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: + """Make a request to the Anthropic API""" + try: + url = f"{self.api_url}/{endpoint}" + headers = { + "x-api-key": self.api_key, + "Content-Type": "application/json", + "anthropic-version": "2023-06-01" + } + + 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 Anthropic API"} + except Exception as e: + return {"success": False, "error": str(e)} + + def list_models(self) -> str: + """List available Anthropic models""" + # Anthropic doesn't have a public models endpoint + # Return known models + output = "Available Anthropic Models:\n" + output += "=" * 50 + "\n" + output += "• claude-3-5-sonnet-20241022\n" + output += "• claude-3-opus-20240229\n" + output += "• claude-3-sonnet-20240229\n" + output += "• claude-3-haiku-20240307\n" + output += "• claude-2.1\n" + output += "• claude-2.0\n" + output += "• claude-instant-1.2\n" + return output + + def get_model_info(self, model_name: str) -> str: + """Get detailed information about a specific model""" + # Anthropic doesn't have a public model info endpoint + # Return basic information + output = f"Model Information: {model_name}\n" + output += "=" * 50 + "\n" + + if "claude-3-5-sonnet" in model_name: + output += "Description: Most intelligent model\n" + output += "Context Window: 200K tokens\n" + output += "Best for: Complex tasks, analysis, coding\n" + elif "claude-3-opus" in model_name: + output += "Description: Most powerful model\n" + output += "Context Window: 200K tokens\n" + output += "Best for: Maximum intelligence\n" + elif "claude-3-sonnet" in model_name: + output += "Description: Balanced performance\n" + output += "Context Window: 200K tokens\n" + output += "Best for: Balance of intelligence and speed\n" + elif "claude-3-haiku" in model_name: + output += "Description: Fastest model\n" + output += "Context Window: 200K tokens\n" + output += "Best for: Quick responses, simple tasks\n" + else: + output += "Description: Claude model\n" + output += "Context Window: Varies\n" + + return output + + def test_connection(self) -> str: + """Test connection to Anthropic""" + try: + # Try to make a simple request + headers = { + "x-api-key": self.api_key, + "Content-Type": "application/json", + "anthropic-version": "2023-06-01" + } + + # Test with a minimal completion request + data = { + "model": "claude-3-haiku-20240307", + "max_tokens": 10, + "messages": [{"role": "user", "content": "Hi"}] + } + + response = requests.post( + f"{self.api_url}/messages", + headers=headers, + json=data, + timeout=10 + ) + + if response.status_code == 200: + return "✅ Anthropic API connection successful." + else: + return f"❌ Anthropic API connection failed: HTTP {response.status_code}" + except Exception as e: + return f"❌ Error connecting to Anthropic: {str(e)}" + + def get_default_model(self) -> str: + """Get the default model for Anthropic""" + return "Default model: claude-3-5-sonnet-20241022 (recommended for most tasks)" + + def set_default_model(self, model_name: str) -> str: + """Set the default model for Anthropic""" + # Anthropic doesn't have a built-in default model concept + # Just verify the model name is valid + known_models = [ + "claude-3-5-sonnet-20241022", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307", + "claude-2.1", + "claude-2.0", + "claude-instant-1.2" + ] + + if model_name in known_models: + return f"✅ Model '{model_name}' is a valid Anthropic model." + else: + return f"⚠️ Model '{model_name}' may not be a valid Anthropic model." + + def get_provider_info(self) -> str: + """Get information about Anthropic configuration""" + output = "Anthropic 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: + headers = { + "x-api-key": self.api_key, + "Content-Type": "application/json", + "anthropic-version": "2023-06-01" + } + + response = requests.post( + f"{self.api_url}/messages", + headers=headers, + json={"model": "claude-3-haiku-20240307", "max_tokens": 1, "messages": [{"role": "user", "content": "test"}]}, + timeout=5 + ) + + if response.status_code == 200: + output += "Status: ✅ Connected\n" + else: + output += f"Status: ❌ Connection failed (HTTP {response.status_code})\n" + except: + output += "Status: ❌ Not connected\n" + + return output + + +class GoogleProviderTools(BaseProviderTools): + """Google provider tools implementation""" + + def __init__(self, api_key: str, **kwargs): + """ + Initialize Google tools + + Args: + api_key: Google API key + **kwargs: Additional configuration + """ + super().__init__("google", api_key=api_key, **kwargs) + self.api_key = api_key + self.api_url = "https://generativelanguage.googleapis.com/v1beta" + + def _make_request(self, endpoint: str, method: str = "GET", data: Optional[Dict] = None) -> Dict[str, Any]: + """Make a request to the Google API""" + try: + url = f"{self.api_url}/{endpoint}?key={self.api_key}" + headers = { + "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 Google API"} + except Exception as e: + return {"success": False, "error": str(e)} + + def list_models(self) -> str: + """List available Google models""" + result = self._make_request("models") + + if not result["success"]: + return f"Error listing models: {result['error']}" + + models = result["data"].get("models", []) + if not models: + return "No models found on Google." + + output = "Available Google Models:\n" + output += "=" * 50 + "\n" + + for model in models: + name = model.get("name", "") + display_name = model.get("displayName", "") + description = model.get("description", "") + + if name: + output += f"• {name}\n" + if display_name: + output += f" Display Name: {display_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""" + # Extract model name from full path if needed + if "/" in model_name: + model_name = model_name.split("/")[-1] + + 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"Name: {model_info.get('name', 'Unknown')}\n" + output += f"Display Name: {model_info.get('displayName', 'Unknown')}\n" + output += f"Description: {model_info.get('description', 'Unknown')}\n" + + if "inputTokenLimit" in model_info: + output += f"Input Token Limit: {model_info['inputTokenLimit']}\n" + + if "outputTokenLimit" in model_info: + output += f"Output Token Limit: {model_info['outputTokenLimit']}\n" + + return output + + def test_connection(self) -> str: + """Test connection to Google""" + try: + # Try to list models as a connection test + result = self._make_request("models") + if result["success"]: + return "✅ Google API connection successful." + else: + return f"❌ Google API connection failed: {result['error']}" + except Exception as e: + return f"❌ Error connecting to Google: {str(e)}" + + def get_default_model(self) -> str: + """Get the default model for Google""" + return "Default model: gemini-1.5-pro (recommended for most tasks)" + + def set_default_model(self, model_name: str) -> str: + """Set the default model for Google""" + # Google 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 Google." + + def get_provider_info(self) -> str: + """Get information about Google configuration""" + output = "Google 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") + provider: Provider name ("ollama", "openai", "openrouter", "anthropic", "google") **kwargs: Provider-specific configuration Returns: @@ -583,6 +908,22 @@ def get_provider_tools(provider: str, **kwargs) -> List[Tool]: tools = OpenRouterProviderTools(api_key=api_key, base_url=base_url) return tools.get_tools() + elif provider == "anthropic": + api_key = kwargs.get("api_key", os.getenv("ANTHROPIC_API_KEY")) + if not api_key: + print("Warning: Anthropic API key not provided") + return [] + tools = AnthropicProviderTools(api_key=api_key) + return tools.get_tools() + + elif provider == "google": + api_key = kwargs.get("api_key", os.getenv("GOOGLE_API_KEY")) + if not api_key: + print("Warning: Google API key not provided") + return [] + tools = GoogleProviderTools(api_key=api_key) + return tools.get_tools() + else: print(f"Warning: Unknown provider '{provider}'") return [] @@ -627,4 +968,24 @@ def get_all_provider_tools() -> List[Tool]: except Exception as e: print(f"⚠️ Could not load OpenRouter tools: {e}") + # Check for Anthropic configuration + anthropic_key = os.getenv("ANTHROPIC_API_KEY") + if anthropic_key: + try: + anthropic_tools = get_provider_tools("anthropic", api_key=anthropic_key) + all_tools.extend(anthropic_tools) + print(f"✅ Added {len(anthropic_tools)} Anthropic tools") + except Exception as e: + print(f"⚠️ Could not load Anthropic tools: {e}") + + # Check for Google configuration + google_key = os.getenv("GOOGLE_API_KEY") + if google_key: + try: + google_tools = get_provider_tools("google", api_key=google_key) + all_tools.extend(google_tools) + print(f"✅ Added {len(google_tools)} Google tools") + except Exception as e: + print(f"⚠️ Could not load Google tools: {e}") + return all_tools \ No newline at end of file