326 lines
9.4 KiB
Python
326 lines
9.4 KiB
Python
"""
|
|
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 fastapi.staticfiles import StaticFiles
|
|
from fastapi.responses import FileResponse
|
|
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=["*"],
|
|
)
|
|
|
|
# Mount static files for web UI
|
|
web_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web")
|
|
if os.path.exists(web_dir):
|
|
app.mount("/static", StaticFiles(directory=web_dir), name="static")
|
|
|
|
# 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 - serve web UI"""
|
|
web_index = os.path.join(web_dir, "index.html")
|
|
if os.path.exists(web_index):
|
|
return FileResponse(web_index)
|
|
else:
|
|
return {
|
|
"message": "MaxClaw Agent API",
|
|
"version": "1.0.0",
|
|
"endpoints": {
|
|
"chat": "/chat",
|
|
"configurations": "/configurations",
|
|
"health": "/health",
|
|
"web_ui": "/static/index.html",
|
|
"monitoring": "/monitoring"
|
|
}
|
|
}
|
|
|
|
|
|
@app.get("/monitoring")
|
|
async def monitoring():
|
|
"""Monitoring dashboard endpoint"""
|
|
monitoring_file = os.path.join(web_dir, "monitoring.html")
|
|
if os.path.exists(monitoring_file):
|
|
return FileResponse(monitoring_file)
|
|
else:
|
|
return {
|
|
"message": "Monitoring dashboard not available",
|
|
"version": "1.0.0",
|
|
"note": "Monitoring dashboard file not found"
|
|
}
|
|
|
|
|
|
@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"]
|
|
},
|
|
{
|
|
"name": "anthropic",
|
|
"description": "Anthropic Claude models",
|
|
"required_env": ["ANTHROPIC_API_KEY"],
|
|
"optional_env": []
|
|
},
|
|
{
|
|
"name": "google",
|
|
"description": "Google Gemini models",
|
|
"required_env": ["GOOGLE_API_KEY"],
|
|
"optional_env": []
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Run the API server
|
|
uvicorn.run(
|
|
"api_server:app",
|
|
host="0.0.0.0",
|
|
port=8000,
|
|
reload=True,
|
|
log_level="info"
|
|
) |