Phase 8: Additional provider integrations (Anthropic, Google) and enhanced documentation

This commit is contained in:
truetype committed 2026-03-20 18:47:02 +01:00
1 parent c44a4ae92a
commit c54f2a33c8
2 files changed
+413 -5

No files matched your search

+51 -4
View File
@@ -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 \
+362 -1
View File
@@ -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