diff --git a/pyproject.toml b/pyproject.toml index b065d7a..25f8526 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "core-api" -version = "1.3.2" +version = "1.3.3" description = "Core Code API - Infrastructure management and tools API" readme = "README.md" requires-python = ">=3.12" diff --git a/src/clients/ai_client.py b/src/clients/ai_client.py deleted file mode 100644 index 9ab377e..0000000 --- a/src/clients/ai_client.py +++ /dev/null @@ -1,197 +0,0 @@ -""" -Core-AI HTTP Client - -Provides interface to Core-AI service for AI performance metrics. -""" -import httpx -from typing import Optional, Dict, List, Any -from src.logging_config import get_logger -from src.config import get_settings - -logger = get_logger(__name__) -settings = get_settings() - - -class CoreAIClient: - """ - HTTP client for Core-AI service - - Provides access to AI performance metrics, tool execution stats, - and memory system monitoring. - """ - - def __init__( - self, - base_url: Optional[str] = None, - timeout: int = 10 - ): - """ - Initialize Core-AI client - - Args: - base_url: Core-AI base URL (default from settings) - timeout: Request timeout in seconds - """ - self.base_url = (base_url or getattr(settings, 'core_ai_base_url', 'http://core-ai:8086')).rstrip("/") - self.timeout = timeout - self.client = httpx.AsyncClient(timeout=self.timeout) - - async def close(self): - """Close the HTTP client""" - await self.client.aclose() - - async def health_check(self) -> bool: - """ - Check if Core-AI service is accessible - - Returns: - True if accessible, False otherwise - """ - try: - response = await self.client.get(f"{self.base_url}/health") - return response.status_code == 200 - except Exception as e: - logger.error(f"Core-AI health check failed: {e}") - return False - - async def get_metrics(self) -> Dict[str, Any]: - """ - Get comprehensive AI performance metrics - - Returns: - Dict with agent performance, tool execution, memory stats - - Example: - { - "uptime_seconds": 3600, - "timestamp": "2025-12-03T20:00:00Z", - "agent": { - "total_requests": 100, - "avg_response_time_ms": 1250.5, - "p95_response_time_ms": 3200.0, - ... - }, - "tools": { - "total_calls": 250, - "success_rate": 0.98, - "top_tools": {...} - }, - "memory": { - "tier1_hit_rate": 0.85, - ... - }, - ... - } - """ - try: - response = await self.client.get(f"{self.base_url}/metrics") - response.raise_for_status() - return response.json() - except httpx.HTTPStatusError as e: - logger.error(f"Failed to get metrics: HTTP {e.response.status_code}") - raise - except Exception as e: - logger.error(f"Failed to get metrics: {e}") - raise - - async def get_recent_errors(self, limit: int = 20) -> List[Dict[str, Any]]: - """ - Get recent request errors - - Args: - limit: Maximum number of errors to return - - Returns: - List of error records with timestamps - - Example: - [ - { - "timestamp": "2025-12-03T19:45:12Z", - "agent_type": "pydantic", - "error": "Connection timeout", - "duration_ms": 5000 - }, - ... - ] - """ - try: - response = await self.client.get( - f"{self.base_url}/metrics/errors", - params={"limit": limit} - ) - response.raise_for_status() - data = response.json() - return data.get("errors", []) - except Exception as e: - logger.error(f"Failed to get recent errors: {e}") - raise - - async def get_tool_failures(self, limit: int = 20) -> List[Dict[str, Any]]: - """ - Get recent tool execution failures - - Args: - limit: Maximum number of failures to return - - Returns: - List of tool failure records - - Example: - [ - { - "timestamp": "2025-12-03T19:50:30Z", - "tool_name": "list_containers", - "error": "Connection refused", - "duration_ms": 150 - }, - ... - ] - """ - try: - response = await self.client.get( - f"{self.base_url}/metrics/tool-failures", - params={"limit": limit} - ) - response.raise_for_status() - data = response.json() - return data.get("failures", []) - except Exception as e: - logger.error(f"Failed to get tool failures: {e}") - raise - - async def reset_metrics(self) -> bool: - """ - Reset all metrics (admin operation) - - Returns: - True if successful - """ - try: - response = await self.client.post(f"{self.base_url}/metrics/reset") - response.raise_for_status() - logger.info("Successfully reset Core-AI metrics") - return True - except Exception as e: - logger.error(f"Failed to reset metrics: {e}") - raise - - async def __aenter__(self): - """Async context manager entry""" - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - """Async context manager exit""" - await self.close() - - -# Singleton instance -_ai_client: Optional[CoreAIClient] = None - - -def get_ai_client() -> CoreAIClient: - """Get singleton Core-AI client instance""" - global _ai_client - if _ai_client is None: - _ai_client = CoreAIClient() - return _ai_client diff --git a/src/config.py b/src/config.py index b210374..0a96c29 100644 --- a/src/config.py +++ b/src/config.py @@ -46,7 +46,7 @@ class Settings(BaseSettings): log_level: str = "DEBUG" # Ollama Configuration (for AI orchestration) - ollama_base_url: str = "http://ollama:11434" + ollama_base_url: str # Required - set OLLAMA_BASE_URL in .env ollama_timeout: int = 300 # 5 minutes # Model Configuration @@ -90,25 +90,22 @@ class Settings(BaseSettings): # Search Configuration search_provider: str = "searxng" - searxng_url: str = "http://searxng:8080" + searxng_url: str # Required - set SEARXNG_URL in .env # Infrastructure Management (Portainer) - portainer_url: str = "http://portainer:9000" - portainer_api_key: str = "" + portainer_url: str # Required - set PORTAINER_URL in .env + portainer_api_key: str # Required - set PORTAINER_API_KEY in .env # Infrastructure Management (Nginx Proxy Manager) - npm_url: str = "http://npm:81" - npm_email: str = "" - npm_password: str = "" + npm_url: str # Required - set NPM_URL in .env + npm_email: str # Required - set NPM_EMAIL in .env + npm_password: str # Required - set NPM_PASSWORD in .env # Home Assistant Configuration - homeassistant_url: str = "http://homeassistant:8123" - homeassistant_token: str = "" + homeassistant_url: str # Required - set HOMEASSISTANT_URL in .env + homeassistant_token: str # Required - set HOMEASSISTANT_TOKEN in .env homeassistant_timeout: int = 30 - # Core-AI Service (AI performance metrics) - core_ai_base_url: str = "http://core-ai:8086" - # OIDC Authentication (Authentik) oidc_enabled: bool = False # Set to True to require authentication oidc_issuer: str = "https://auth.schweitz.net/application/o/core-api/" diff --git a/src/controllers/ai_controller.py b/src/controllers/ai_controller.py deleted file mode 100644 index 2a52b9f..0000000 --- a/src/controllers/ai_controller.py +++ /dev/null @@ -1,219 +0,0 @@ -""" -AI Metrics Proxy Controller - -Provides proxy endpoints to Core-AI service metrics. -Allows external access to AI performance stats via core-api. -""" -from fastapi import APIRouter, HTTPException -from typing import Dict, List, Any -from src.clients.ai_client import get_ai_client -from src.logging_config import get_logger - -logger = get_logger(__name__) - -# Create router -router = APIRouter( - prefix="/ai", - tags=["AI Metrics"] -) - - -@router.get( - "/health", - summary="Check Core-AI service health", - description="Verify that the Core-AI service is accessible and responding" -) -async def ai_health_check(): - """ - Check if Core-AI service is healthy - - Returns: - Health status and availability - """ - try: - ai_client = get_ai_client() - is_healthy = await ai_client.health_check() - - return { - "service": "core-ai", - "status": "healthy" if is_healthy else "unhealthy", - "accessible": is_healthy - } - - except Exception as e: - logger.error(f"AI health check failed: {e}") - return { - "service": "core-ai", - "status": "error", - "accessible": False, - "error": str(e) - } - - -@router.get( - "/metrics", - response_model=Dict[str, Any], - summary="Get comprehensive AI performance metrics", - description="Returns detailed metrics including agent performance, tool execution stats, memory system metrics, and user activity" -) -async def get_ai_metrics(): - """ - Proxy endpoint for Core-AI metrics - - Returns comprehensive AI performance data: - - Agent request statistics (total, by type, response times) - - Response time percentiles (p50, p95, p99) - - Tool execution metrics (calls, success rates, durations) - - Memory system statistics (cache hits, consolidations) - - User activity tracking - - Concurrency metrics - - Returns: - Dict with all collected metrics - - Raises: - HTTPException: If Core-AI is unreachable or returns error - """ - try: - ai_client = get_ai_client() - metrics = await ai_client.get_metrics() - return metrics - - except Exception as e: - logger.error(f"Failed to fetch AI metrics: {e}") - raise HTTPException( - status_code=503, - detail=f"Core-AI service unavailable: {str(e)}" - ) - - -@router.get( - "/metrics/errors", - response_model=Dict[str, Any], - summary="Get recent request errors", - description="Returns recent AI agent request errors with timestamps and details" -) -async def get_ai_errors(limit: int = 20): - """ - Get recent AI request errors - - Args: - limit: Maximum number of errors to return (default: 20) - - Returns: - Dict with error list and total count - - Example response: - { - "errors": [ - { - "timestamp": "2025-12-03T19:45:12Z", - "agent_type": "pydantic", - "error": "Connection timeout", - "duration_ms": 5000 - } - ], - "total": 1 - } - """ - try: - ai_client = get_ai_client() - errors = await ai_client.get_recent_errors(limit=limit) - - return { - "errors": errors, - "total": len(errors) - } - - except Exception as e: - logger.error(f"Failed to fetch AI errors: {e}") - raise HTTPException( - status_code=503, - detail=f"Core-AI service unavailable: {str(e)}" - ) - - -@router.get( - "/metrics/tool-failures", - response_model=Dict[str, Any], - summary="Get recent tool execution failures", - description="Returns recent tool execution failures with error details" -) -async def get_ai_tool_failures(limit: int = 20): - """ - Get recent tool execution failures - - Args: - limit: Maximum number of failures to return (default: 20) - - Returns: - Dict with failure list and total count - - Example response: - { - "failures": [ - { - "timestamp": "2025-12-03T19:50:30Z", - "tool_name": "list_containers", - "error": "Connection refused", - "duration_ms": 150 - } - ], - "total": 1 - } - """ - try: - ai_client = get_ai_client() - failures = await ai_client.get_tool_failures(limit=limit) - - return { - "failures": failures, - "total": len(failures) - } - - except Exception as e: - logger.error(f"Failed to fetch tool failures: {e}") - raise HTTPException( - status_code=503, - detail=f"Core-AI service unavailable: {str(e)}" - ) - - -@router.post( - "/metrics/reset", - summary="Reset all AI metrics (admin)", - description="Clear all collected metrics. This is an administrative operation that resets all counters and history." -) -async def reset_ai_metrics(): - """ - Reset all AI metrics (admin operation) - - Clears all collected metrics including: - - Request history - - Tool execution stats - - Memory system metrics - - Error logs - - Returns: - Success confirmation - - Note: - This is an administrative operation that should be used carefully. - All historical data will be lost. - """ - try: - ai_client = get_ai_client() - await ai_client.reset_metrics() - - logger.info("AI metrics reset successfully") - return { - "success": True, - "message": "AI metrics reset successfully" - } - - except Exception as e: - logger.error(f"Failed to reset AI metrics: {e}") - raise HTTPException( - status_code=503, - detail=f"Core-AI service unavailable: {str(e)}" - ) diff --git a/src/controllers/health_controller.py b/src/controllers/health_controller.py index e9299b4..c033d88 100644 --- a/src/controllers/health_controller.py +++ b/src/controllers/health_controller.py @@ -11,9 +11,6 @@ from src.config import get_settings from src.logging_config import get_logger from src.models.ollama_client import get_ollama_client -# Note: Agent functionality moved to separate core-ai service (Dec 2025) -# This service (core-api) only provides infrastructure management and tools -AGENT_AVAILABLE = False logger = get_logger(__name__) @@ -134,11 +131,6 @@ class HealthController(BaseController): ollama_error = str(e) logger.warning(f"Ollama health check failed: {ollama_error}") - # Note: Agent functionality moved to separate core-ai service - # This service only needs Ollama for embeddings (infrastructure tools) - # Agent health is checked separately in core-ai service - - # Determine overall status (only Ollama required for core-api) is_healthy = ollama_healthy elapsed_ms = int((time.time() - start_time) * 1000) @@ -157,8 +149,7 @@ class HealthController(BaseController): "available": False }, "error": ollama_error - }, - "note": "AI agent functionality available in separate core-ai service (port 8086)" + } } } @@ -204,20 +195,7 @@ class HealthController(BaseController): "error": str(e) } - # 2. Agent Stack - Moved to separate core-ai service - diagnostics["components"]["agent"] = { - "status": "N/A", - "note": "AI agent functionality moved to separate core-ai service (port 8086)", - "check_url": "http://core-ai:8086/health" - } - - # 3. Memory System (Qdrant) - Moved to core-ai service - diagnostics["components"]["qdrant"] = { - "status": "N/A", - "note": "Memory system managed by core-ai service (port 8086)" - } - - # 4. Configuration + # 2. Configuration diagnostics["configuration"] = { "agent_fallback_enabled": settings.agent_fallback_enabled, "memory_tier1_max_turns": settings.memory_tier1_max_turns, diff --git a/src/main.py b/src/main.py index 19a82f1..80e4f6f 100644 --- a/src/main.py +++ b/src/main.py @@ -13,7 +13,6 @@ from src.controllers.infrastructure_controller import infrastructure_controller from src.controllers.tools_controller import tools_controller from src.controllers.health_controller import health_controller from src.controllers.static_controller import static_controller -from src.controllers.ai_controller import router as ai_router from src.controllers.housekeeping_controller import housekeeping_controller from src.security import initialize_oidc @@ -101,7 +100,6 @@ app.include_router(tools_controller.router) # /tools/* app.include_router(infrastructure_controller.router) # /infrastructure/* app.include_router(housekeeping_controller.router) # /housekeeping/* app.include_router(static_controller.router) # /static/* -app.include_router(ai_router) # /ai/* # Global exception handler