Files
mcphost-api/serve_mcphost.py
2025-05-11 19:38:50 +03:00

172 lines
5.4 KiB
Python

import asyncio
import socket
import subprocess
import time
from contextlib import asynccontextmanager
from typing import List
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from commons.mcp_manager import MCPHostManager
from commons.logging_utils import setup_logger
from commons.openai_utils import ChatMessage, ChatCompletionRequest, AVAILABLE_MODELS, generate_id, stream_response
from commons.settings import settings
# Setup logger
logger = setup_logger()
# Initialize the MCPHost manager
mcp_manager = MCPHostManager()
async def process_with_mcphost(messages: List[ChatMessage], model: str) -> str:
"""Process messages using MCPHost"""
# Get the last user message
last_user_message = next((msg.content for msg in reversed(messages) if msg.role == "user"), "")
if not last_user_message:
return "No user message found"
# Send to MCPHost and get response
response = await mcp_manager.send_prompt_async(last_user_message)
return response
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage application lifespan"""
# Startup
logger.info("=" * 50)
logger.info("MCPHost OpenAI-compatible API Server v1.0")
logger.info("Debug Mode: {}", "ON" if settings.debug else "OFF")
logger.info("=" * 50)
if not mcp_manager.start():
logger.error("Failed to start MCPHost")
# You might want to exit or handle this differently
else:
logger.success("MCPHost started successfully")
yield
# Shutdown
logger.info("Shutting down MCPHost...")
mcp_manager.shutdown()
logger.success("Shutdown complete")
app = FastAPI(title="MCPHost OpenAI-compatible API", lifespan=lifespan)
@app.get("/v1/models")
async def list_models():
"""List all available models"""
return {
"object": "list",
"data": AVAILABLE_MODELS
}
@app.get("/v1/models/{model_id}")
async def get_model(model_id: str):
"""Get details of a specific model"""
model = next((m for m in AVAILABLE_MODELS if m["id"] == model_id), None)
if not model:
raise HTTPException(status_code=404, detail=f"Model {model_id} not found")
return model
@app.post("/v1/chat/completions")
async def chat_completions(request: ChatCompletionRequest):
# Validate model exists
if not any(model["id"] == request.model for model in AVAILABLE_MODELS):
raise HTTPException(status_code=404, detail=f"Model {request.model} not found")
response_content = await process_with_mcphost(request.messages, request.model)
if not request.stream:
return {
"id": f"chatcmpl-{generate_id()}",
"object": "chat.completion",
"created": int(time.time()),
"model": request.model,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": response_content
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": len(" ".join([msg.content for msg in request.messages]).split()),
"completion_tokens": len(response_content.split()),
"total_tokens": len(" ".join([msg.content for msg in request.messages]).split()) + len(
response_content.split())
}
}
else:
return StreamingResponse(
stream_response(response_content, request.model),
media_type="text/event-stream"
)
@app.get("/")
async def root():
return {"message": "MCPHost OpenAI-compatible API server. Visit /docs for documentation."}
@app.get("/health")
async def health_check():
return {"status": "healthy", "mcphost_alive": mcp_manager._is_alive()}
@app.post("/restart-service")
async def restart_service():
"""Restart the llm-api-mcphost service using supervisorctl"""
hostname = socket.gethostname()
if hostname == "afsar":
async def delayed_restart():
await asyncio.sleep(settings.restart_delay)
try:
result = await asyncio.create_subprocess_exec(
"sudo", "supervisorctl", "restart", "llm-api-mcphost",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await result.communicate()
if result.returncode != 0:
logger.error(f"Failed to restart service: {stderr.decode()}")
else:
logger.info("Successfully restarted llm-api-mcphost service")
except Exception as e:
logger.error(f"Error restarting service: {str(e)}")
# Schedule the restart without waiting for it
asyncio.create_task(delayed_restart())
logger.info("Service restart scheduled")
return {
"status": "scheduled",
"message": "Service restart has been scheduled",
"hostname": hostname,
"note": "Service will restart in 1 second"
}
else:
logger.info(f"Restart request ignored - hostname is '{hostname}', not 'afsar'")
return {
"status": "ignored",
"message": "Service restart not performed - incorrect hostname",
"hostname": hostname,
"required_hostname": "afsar"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host=settings.host, port=settings.port)