Phase 5: Docker containerization and API server for remote access
This commit is contained in:
1 parent
b17c180fbe
commit
5541770896
6 files changed
+578
-1
No files matched your search
@@ -0,0 +1,77 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.tox
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.log
|
||||
.git
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.hypothesis
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
.venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
.dockerignore
|
||||
|
||||
# Documentation
|
||||
docs/
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# Tests
|
||||
tests/
|
||||
test_*.py
|
||||
|
||||
# Completions
|
||||
completions/
|
||||
|
||||
# Configuration examples
|
||||
*.example
|
||||
*.example.*
|
||||
|
||||
# Backup files
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
.tmp/
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# MaxClaw Agent Docker Image
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy requirements first for better caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create directory for configuration
|
||||
RUN mkdir -p /root/.maxclaw && \
|
||||
chmod 700 /root/.maxclaw
|
||||
|
||||
# Expose port for API (future use)
|
||||
EXPOSE 8000
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV MODEL_PROVIDER=ollama
|
||||
ENV MODEL_NAME=gemma2:9b
|
||||
ENV OLLAMA_BASE_URL=http://host.docker.internal:11434
|
||||
|
||||
# Default command
|
||||
CMD ["python", "main.py"]
|
||||
@@ -13,15 +13,36 @@ Un agente configurabile basato su DeepAgents che supporta più provider LLM.
|
||||
- **Strumenti personalizzati**: Include strumenti per gestire Proxmox e Ollama
|
||||
- **Completamento shell**: Supporta bash e zsh per autocompletamento dei comandi
|
||||
- **Test completi**: Suite di test per garantire la qualità del codice
|
||||
- **Docker support**: Containerizzazione per deployment facile
|
||||
- **API REST**: Server API per accesso remoto
|
||||
|
||||
## Installazione
|
||||
|
||||
### Installazione Locale
|
||||
|
||||
1. Clona questo repository
|
||||
2. Installa i pacchetti richiesti:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Installazione con Docker
|
||||
|
||||
```bash
|
||||
# Build dell'immagine
|
||||
docker build -t maxclaw-agent .
|
||||
|
||||
# Esecuzione con Docker Compose
|
||||
docker-compose up -d
|
||||
|
||||
# Esecuzione diretta
|
||||
docker run -it --rm \
|
||||
-e MODEL_PROVIDER=ollama \
|
||||
-e MODEL_NAME=gemma2:9b \
|
||||
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
|
||||
maxclaw-agent
|
||||
```
|
||||
|
||||
### Configurazione del Completamento Shell
|
||||
|
||||
#### Bash
|
||||
@@ -128,6 +149,8 @@ La TUI ora include:
|
||||
|
||||
## Utilizzo
|
||||
|
||||
### Utilizzo Locale
|
||||
|
||||
Esegui l'agente:
|
||||
```bash
|
||||
python main.py
|
||||
@@ -135,6 +158,64 @@ python main.py
|
||||
|
||||
Puoi quindi interagire con l'agente nel terminale. Digita 'exit' per uscire.
|
||||
|
||||
### Utilizzo con Docker
|
||||
|
||||
```bash
|
||||
# Avvia con Docker Compose
|
||||
docker-compose up -d
|
||||
|
||||
# Accedi al terminale
|
||||
docker exec -it maxclaw-agent python main.py
|
||||
|
||||
# Oppure esegui direttamente
|
||||
docker run -it --rm maxclaw-agent
|
||||
```
|
||||
|
||||
### Utilizzo dell'API REST
|
||||
|
||||
Avvia il server API:
|
||||
```bash
|
||||
python api_server.py
|
||||
```
|
||||
|
||||
L'API sarà disponibile su http://localhost:8000
|
||||
|
||||
#### Endpoint API Disponibili
|
||||
|
||||
- `GET /`: Informazioni sull'API
|
||||
- `GET /health`: Health check
|
||||
- `POST /chat`: Chat con l'agente
|
||||
- `GET /configurations`: Elenca tutte le configurazioni
|
||||
- `POST /configurations`: Aggiungi una configurazione
|
||||
- `DELETE /configurations/{name}`: Rimuovi una configurazione
|
||||
- `POST /configurations/{name}/activate`: Attiva una configurazione
|
||||
- `GET /configurations/active`: Mostra la configurazione attiva
|
||||
- `POST /configurations/{name}/test`: Testa una configurazione
|
||||
- `GET /providers`: Elenca i provider disponibili
|
||||
|
||||
#### Esempio di Utilizzo dell'API
|
||||
|
||||
```bash
|
||||
# Chat con l'agente
|
||||
curl -X POST http://localhost:8000/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"message": "Ciao, chi sei?"}'
|
||||
|
||||
# Elenca configurazioni
|
||||
curl http://localhost:8000/configurations
|
||||
|
||||
# Aggiungi una configurazione
|
||||
curl -X POST http://localhost:8000/configurations \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "mio-ollama",
|
||||
"provider": "ollama",
|
||||
"model_name": "gemma2:9b",
|
||||
"base_url": "http://192.168.0.50:11434",
|
||||
"set_active": true
|
||||
}'
|
||||
```
|
||||
|
||||
## 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.
|
||||
@@ -234,6 +315,42 @@ pytest tests/test_provider_tools.py
|
||||
pytest tests/ -v
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
### Build dell'Immagine
|
||||
```bash
|
||||
docker build -t maxclaw-agent .
|
||||
```
|
||||
|
||||
### Esecuzione con Docker Compose
|
||||
```bash
|
||||
# Avvia tutti i servizi
|
||||
docker-compose up -d
|
||||
|
||||
# Visualizza i log
|
||||
docker-compose logs -f
|
||||
|
||||
# Ferma i servizi
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Variabili d'Ambiente Docker
|
||||
|
||||
Puoi configurare il container Docker con le seguenti variabili d'ambiente:
|
||||
|
||||
```bash
|
||||
docker run -it --rm \
|
||||
-e MODEL_PROVIDER=ollama \
|
||||
-e MODEL_NAME=gemma2:9b \
|
||||
-e OLLAMA_BASE_URL=http://host.docker.internal:11434 \
|
||||
-e OPENAI_API_KEY=sk-... \
|
||||
-e OPENROUTER_API_KEY=sk-or-... \
|
||||
-e PROXMOX_HOST=192.168.0.100 \
|
||||
-e PROXMOX_USER=root \
|
||||
-e PROXMOX_PASSWORD=password \
|
||||
maxclaw-agent
|
||||
```
|
||||
|
||||
## Personalizzazione
|
||||
|
||||
Per personalizzare l'agente, modifica `main.py`:
|
||||
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
MaxClaw Agent API Server
|
||||
Provides REST API for remote access to MaxClaw Agent
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Dict, Any, List
|
||||
from fastapi import FastAPI, HTTPException, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
import uvicorn
|
||||
|
||||
# Add current directory to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from config_manager import config_manager
|
||||
from main import create_maxclaw_agent
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title="MaxClaw Agent API",
|
||||
description="REST API for MaxClaw Agent - configurable AI agent",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# Add CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Global agent instance
|
||||
agent = None
|
||||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
"""Chat request model"""
|
||||
message: str
|
||||
conversation_id: Optional[str] = None
|
||||
provider: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
"""Chat response model"""
|
||||
response: str
|
||||
conversation_id: Optional[str] = None
|
||||
provider: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
|
||||
|
||||
class ConfigRequest(BaseModel):
|
||||
"""Configuration request model"""
|
||||
name: str
|
||||
provider: str
|
||||
model_name: str
|
||||
api_key: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
set_active: bool = False
|
||||
|
||||
|
||||
class ConfigResponse(BaseModel):
|
||||
"""Configuration response model"""
|
||||
name: str
|
||||
provider: str
|
||||
model_name: str
|
||||
is_active: bool
|
||||
|
||||
|
||||
def get_agent():
|
||||
"""Get or create agent instance"""
|
||||
global agent
|
||||
if agent is None:
|
||||
try:
|
||||
agent = create_maxclaw_agent()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to create agent: {str(e)}")
|
||||
return agent
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialize agent on startup"""
|
||||
global agent
|
||||
try:
|
||||
agent = create_maxclaw_agent()
|
||||
print("✅ MaxClaw Agent API server started")
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to initialize agent: {e}")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Root endpoint"""
|
||||
return {
|
||||
"message": "MaxClaw Agent API",
|
||||
"version": "1.0.0",
|
||||
"endpoints": {
|
||||
"chat": "/chat",
|
||||
"configurations": "/configurations",
|
||||
"health": "/health"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "agent_initialized": agent is not None}
|
||||
|
||||
|
||||
@app.post("/chat", response_model=ChatResponse)
|
||||
async def chat(request: ChatRequest):
|
||||
"""Chat with MaxClaw Agent"""
|
||||
try:
|
||||
agent_instance = get_agent()
|
||||
|
||||
# Create message
|
||||
message = HumanMessage(content=request.message)
|
||||
|
||||
# Invoke agent
|
||||
result = agent_instance.invoke({"messages": [message]})
|
||||
|
||||
# Extract response
|
||||
ai_message = result["messages"][-1]
|
||||
|
||||
return ChatResponse(
|
||||
response=ai_message.content,
|
||||
conversation_id=request.conversation_id,
|
||||
provider=os.getenv("MODEL_PROVIDER", "unknown"),
|
||||
model=os.getenv("MODEL_NAME", "unknown")
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/configurations", response_model=List[ConfigResponse])
|
||||
async def list_configurations():
|
||||
"""List all configurations"""
|
||||
try:
|
||||
configs = config_manager.get_configurations()
|
||||
active = config_manager.get_active_configuration()
|
||||
|
||||
result = []
|
||||
for name, config in configs.items():
|
||||
result.append(ConfigResponse(
|
||||
name=name,
|
||||
provider=config.get("provider", "unknown"),
|
||||
model_name=config.get("model_name", "unknown"),
|
||||
is_active=(name == active)
|
||||
))
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/configurations", response_model=ConfigResponse)
|
||||
async def add_configuration(request: ConfigRequest):
|
||||
"""Add a new configuration"""
|
||||
try:
|
||||
config = {
|
||||
"provider": request.provider,
|
||||
"model_name": request.model_name
|
||||
}
|
||||
|
||||
if request.api_key:
|
||||
config["api_key"] = request.api_key
|
||||
|
||||
if request.base_url:
|
||||
config["base_url"] = request.base_url
|
||||
|
||||
# Add configuration
|
||||
config_manager.add_configuration(request.name, config)
|
||||
|
||||
# Set as active if requested
|
||||
if request.set_active:
|
||||
config_manager.set_active_configuration(request.name)
|
||||
|
||||
return ConfigResponse(
|
||||
name=request.name,
|
||||
provider=request.provider,
|
||||
model_name=request.model_name,
|
||||
is_active=request.set_active
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.delete("/configurations/{name}")
|
||||
async def delete_configuration(name: str):
|
||||
"""Delete a configuration"""
|
||||
try:
|
||||
if config_manager.remove_configuration(name):
|
||||
return {"message": f"Configuration '{name}' deleted successfully"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"Configuration '{name}' not found")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/configurations/{name}/activate")
|
||||
async def activate_configuration(name: str):
|
||||
"""Set a configuration as active"""
|
||||
try:
|
||||
if config_manager.set_active_configuration(name):
|
||||
return {"message": f"Configuration '{name}' set as active"}
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"Configuration '{name}' not found")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/configurations/active")
|
||||
async def get_active_configuration():
|
||||
"""Get the active configuration"""
|
||||
try:
|
||||
active_name = config_manager.get_active_configuration()
|
||||
if not active_name:
|
||||
return {"message": "No active configuration set"}
|
||||
|
||||
config = config_manager.get_configuration(active_name)
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail=f"Active configuration '{active_name}' not found")
|
||||
|
||||
return {
|
||||
"name": active_name,
|
||||
"provider": config.get("provider", "unknown"),
|
||||
"model_name": config.get("model_name", "unknown")
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/configurations/{name}/test")
|
||||
async def test_configuration(name: str):
|
||||
"""Test connection to a configuration"""
|
||||
try:
|
||||
success, message = config_manager.test_configuration_connection(name)
|
||||
return {
|
||||
"success": success,
|
||||
"message": message
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.get("/providers")
|
||||
async def list_providers():
|
||||
"""List available providers"""
|
||||
return {
|
||||
"providers": [
|
||||
{
|
||||
"name": "openai",
|
||||
"description": "OpenAI GPT models",
|
||||
"required_env": ["OPENAI_API_KEY"],
|
||||
"optional_env": []
|
||||
},
|
||||
{
|
||||
"name": "ollama",
|
||||
"description": "Local Ollama models",
|
||||
"required_env": [],
|
||||
"optional_env": ["OLLAMA_BASE_URL"]
|
||||
},
|
||||
{
|
||||
"name": "openrouter",
|
||||
"description": "OpenRouter multi-provider access",
|
||||
"required_env": ["OPENROUTER_API_KEY"],
|
||||
"optional_env": ["OPENROUTER_BASE_URL"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the API server
|
||||
uvicorn.run(
|
||||
"api_server:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True,
|
||||
log_level="info"
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
maxclaw:
|
||||
build: .
|
||||
container_name: maxclaw-agent
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
# Mount configuration directory for persistence
|
||||
- maxclaw-config:/root/.maxclaw
|
||||
# Mount current directory for development (optional)
|
||||
# - .:/app
|
||||
environment:
|
||||
# Model configuration
|
||||
- MODEL_PROVIDER=${MODEL_PROVIDER:-ollama}
|
||||
- MODEL_NAME=${MODEL_NAME:-gemma2:9b}
|
||||
- OLLAMA_BASE_URL=${OLLAMA_BASE_URL:-http://host.docker.internal:11434}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-}
|
||||
- OPENROUTER_BASE_URL=${OPENROUTER_BASE_URL:-https://openrouter.ai/api/v1}
|
||||
|
||||
# Proxmox configuration (optional)
|
||||
- PROXMOX_HOST=${PROXMOX_HOST:-}
|
||||
- PROXMOX_USER=${PROXMOX_USER:-root}
|
||||
- PROXMOX_PASSWORD=${PROXMOX_PASSWORD:-}
|
||||
- PROXMOX_TOKEN=${PROXMOX_TOKEN:-}
|
||||
networks:
|
||||
- maxclaw-network
|
||||
# Interactive mode for terminal usage
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
# Optional: Ollama service (if you want to run Ollama in Docker)
|
||||
# ollama:
|
||||
# image: ollama/ollama:latest
|
||||
# container_name: ollama
|
||||
# restart: unless-stopped
|
||||
# volumes:
|
||||
# - ollama-data:/root/.ollama
|
||||
# ports:
|
||||
# - "11434:11434"
|
||||
# networks:
|
||||
# - maxclaw-network
|
||||
|
||||
volumes:
|
||||
maxclaw-config:
|
||||
driver: local
|
||||
# ollama-data:
|
||||
# driver: local
|
||||
|
||||
networks:
|
||||
maxclaw-network:
|
||||
driver: bridge
|
||||
+8
-1
@@ -1,4 +1,11 @@
|
||||
deepagents>=0.0.34
|
||||
langchain>=0.3.0
|
||||
langchain-community>=0.3.0
|
||||
langchain-openai>=0.1.0
|
||||
langchain-openai>=0.1.0
|
||||
cryptography>=41.0.0
|
||||
requests>=2.31.0
|
||||
pyyaml>=6.0
|
||||
fastapi>=0.104.0
|
||||
uvicorn>=0.24.0
|
||||
pydantic>=2.5.0
|
||||
pytest>=7.4.0
|
||||
Reference in new issue
Block a user