Phase 8: Additional provider integrations (Anthropic, Google) and enhanced documentation
This commit is contained in:
1 parent
c44a4ae92a
commit
c54f2a33c8
2 files changed
+413
-5
No files matched your search
@@ -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
@@ -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
|
||||
Reference in new issue
Block a user