Implements Phase 2 of AI performance monitoring - creating a visual
dashboard widget for Organizr to display real-time AI metrics.
New Components:
- src/clients/ai_client.py: HTTP client for Core-AI service
- Async HTTP requests to core-ai:8086
- Fetches metrics, errors, and tool failures
- Health check and metrics reset operations
- src/controllers/ai_controller.py: Proxy controller for AI metrics
- GET /ai/health - Core-AI health check
- GET /ai/metrics - Comprehensive performance metrics (proxied)
- GET /ai/metrics/errors - Recent request errors (proxied)
- GET /ai/metrics/tool-failures - Tool execution failures (proxied)
- POST /ai/metrics/reset - Reset all metrics (admin)
- static/widgets/ai-stats.html: Performance dashboard widget
- 4-panel grid layout: Agent, Tools, Memory, Health
- Real-time metrics with 10-second auto-refresh
- Color-coded performance indicators (excellent/good/warning/critical)
- Response time thresholds: <1s excellent, <3s good, <10s warning
- Success rate thresholds: >99% excellent, >95% good, >90% warning
- Top 5 tools display with call counts and success rates
- Transparent background for Organizr dark theme
- Responsive design with mobile support
Configuration:
- src/config.py: Added core_ai_base_url setting
- src/main.py: Registered ai_router for /ai/* endpoints
Architecture:
┌─────────────────────────────────────────────┐
│ Browser (Organizr iFrame) │
│ ↓ Fetches /ai/metrics │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ core-api:8083 (api.schweitz.net) │
│ - Serves widget HTML │
│ - Proxies metrics requests │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ core-ai:8086 (internal) │
│ - Collects metrics │
│ - Returns JSON data │
└─────────────────────────────────────────────┘
Benefits:
- External access via api.schweitz.net (proxy approach)
- No CORS issues (same-origin requests)
- Core-AI remains internal-only
- Single integration point with Organizr
Integration with Organizr:
1. Go to Settings → Customize → Homepage Items
2. Add New Item:
- Name: "AI Performance Stats"
- Type: iFrame
- URL: http://localhost:8083/static/widgets/ai-stats.html
- Authentication: User
3. Position widget on dashboard
Tested:
✅ Proxy endpoints responding correctly
✅ Widget accessible via /static/widgets/
✅ Metrics data flowing from core-ai → core-api → browser
✅ Color coding and formatting working
✅ Auto-refresh functional
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
158 lines
5.3 KiB
Python
158 lines
5.3 KiB
Python
"""
|
|
Global configuration for Core Code API
|
|
"""
|
|
from pydantic_settings import BaseSettings
|
|
from functools import lru_cache
|
|
|
|
# Import infrastructure credentials from gitignored module
|
|
try:
|
|
from src.credentials import (
|
|
PORTAINER_URL, PORTAINER_API_KEY,
|
|
NPM_URL, NPM_EMAIL, NPM_PASSWORD,
|
|
KUMA_URL, KUMA_USERNAME, KUMA_PASSWORD, KUMA_API_KEY,
|
|
BRAVE_SEARCH_API_KEY,
|
|
GOOGLE_SEARCH_API_KEY, GOOGLE_SEARCH_ENGINE_ID
|
|
)
|
|
except ImportError:
|
|
# Fallback to empty strings if credentials.py doesn't exist
|
|
# (e.g., fresh clone before credentials setup)
|
|
PORTAINER_URL = "http://localhost:8001"
|
|
PORTAINER_API_KEY = ""
|
|
NPM_URL = "http://localhost:81"
|
|
NPM_EMAIL = ""
|
|
NPM_PASSWORD = ""
|
|
KUMA_URL = "http://localhost:3001"
|
|
KUMA_USERNAME = ""
|
|
KUMA_PASSWORD = ""
|
|
KUMA_API_KEY = ""
|
|
BRAVE_SEARCH_API_KEY = ""
|
|
GOOGLE_SEARCH_API_KEY = ""
|
|
GOOGLE_SEARCH_ENGINE_ID = ""
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
"""Global application settings"""
|
|
|
|
# Application
|
|
app_name: str = "Core Code API"
|
|
app_version: str = "1.0.0"
|
|
debug: bool = False
|
|
|
|
# Server
|
|
host: str = "0.0.0.0"
|
|
port: int = 8083
|
|
|
|
# CORS
|
|
cors_origins: list[str] = ["*"]
|
|
cors_credentials: bool = True
|
|
cors_methods: list[str] = ["*"]
|
|
cors_headers: list[str] = ["*"]
|
|
|
|
# Logging
|
|
log_level: str = "DEBUG"
|
|
|
|
# Ollama Configuration (for AI orchestration)
|
|
ollama_base_url: str = "http://ollama:11434"
|
|
ollama_timeout: int = 300 # 5 minutes
|
|
|
|
# Model Configuration
|
|
default_model: str = "mistral-tools:7b"
|
|
agent_model: str = "gemma2:9b-instruct-q5_K_M" # Must support tool calling with ADK (~4GB VRAM)
|
|
lightweight_models: str = "gemma3-tools:1b,phi3:mini"
|
|
heavy_models: str = "mistral:7b,gemma2:9b,gemma3:12b,mixtral:8x7b"
|
|
code_models: str = "codestral:latest,codegemma:latest"
|
|
# Previous config (gemma3:12b used ~10GB VRAM)
|
|
# default_model: str = "gemma3:12b"
|
|
# agent_model: str = "gemma3:12b"
|
|
|
|
# System Prompt Variant (for A/B testing)
|
|
# Options: v1_verbose, v2_concise, v3_imperative, v4_minimal, v4_gemini_suggestion, v5_adk_optimized, v7_adk_best_practice, v8_holistic
|
|
system_prompt_variant: str = "v8_holistic"
|
|
|
|
# Agent Configuration
|
|
agent_fallback_enabled: bool = True
|
|
|
|
# Model Aliases (OpenAI → Local)
|
|
alias_gpt35: str = "gemma:7b"
|
|
alias_gpt4: str = "mistral:7b"
|
|
alias_gpt4_turbo: str = "mixtral:8x7b"
|
|
alias_gpt4_code: str = "codestral:latest"
|
|
|
|
# Memory Configuration
|
|
memory_tier1_max_turns: int = 10
|
|
memory_consolidation_threshold: int = 10
|
|
|
|
# Qdrant Configuration
|
|
qdrant_host: str = "qdrant"
|
|
qdrant_port: int = 6333
|
|
qdrant_collection_conversations: str = "core_api_conversations"
|
|
qdrant_collection_documents: str = "core_api_documents"
|
|
qdrant_collection_user_facts: str = "core_api_user_facts"
|
|
|
|
# Embeddings (using Ollama - no local models needed)
|
|
embedding_model: str = "nomic-embed-text" # Ollama embedding model
|
|
embedding_dimension: int = 768 # nomic-embed-text dimension
|
|
embedding_batch_size: int = 32
|
|
|
|
# Search Configuration
|
|
search_provider: str = "google" # Options: google, brave, searxng, duckduckgo
|
|
searxng_url: str = "http://searxng:8080" # For future self-hosted SearxNG
|
|
|
|
# Search API Keys (from credentials.py)
|
|
brave_search_api_key: str = BRAVE_SEARCH_API_KEY # https://brave.com/search/api/
|
|
google_search_api_key: str = GOOGLE_SEARCH_API_KEY # https://console.cloud.google.com/
|
|
google_search_engine_id: str = GOOGLE_SEARCH_ENGINE_ID # Custom Search Engine ID
|
|
|
|
# Infrastructure Management (from credentials.py)
|
|
portainer_url: str = PORTAINER_URL
|
|
portainer_api_key: str = PORTAINER_API_KEY
|
|
|
|
npm_url: str = NPM_URL
|
|
npm_email: str = NPM_EMAIL
|
|
npm_password: str = NPM_PASSWORD
|
|
|
|
kuma_url: str = KUMA_URL
|
|
kuma_username: str = KUMA_USERNAME
|
|
kuma_password: str = KUMA_PASSWORD
|
|
kuma_api_key: str = KUMA_API_KEY
|
|
|
|
# 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/"
|
|
oidc_audience: str = "core-api"
|
|
|
|
@property
|
|
def model_aliases(self) -> dict:
|
|
"""Computed property for model aliases"""
|
|
return {
|
|
"gpt-3.5-turbo": self.alias_gpt35,
|
|
"gpt-4": self.alias_gpt4,
|
|
"gpt-4-turbo": self.alias_gpt4_turbo,
|
|
"gpt-4-code": self.alias_gpt4_code,
|
|
}
|
|
|
|
def get_lightweight_models(self) -> list[str]:
|
|
"""Parse comma-separated lightweight models"""
|
|
return [m.strip().strip('"').strip("'") for m in self.lightweight_models.split(",") if m.strip()]
|
|
|
|
def get_heavy_models(self) -> list[str]:
|
|
"""Parse comma-separated heavy models"""
|
|
return [m.strip().strip('"').strip("'") for m in self.heavy_models.split(",") if m.strip()]
|
|
|
|
def get_code_models(self) -> list[str]:
|
|
"""Parse comma-separated code models"""
|
|
return [m.strip().strip('"').strip("'") for m in self.code_models.split(",") if m.strip()]
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
case_sensitive = False
|
|
|
|
|
|
@lru_cache()
|
|
def get_settings() -> Settings:
|
|
"""Cached settings instance"""
|
|
return Settings()
|