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
+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"
|
||||
)
|
||||
Reference in new issue
Block a user