Initial commit

This commit is contained in:
Jordan
2026-04-01 23:16:45 +01:00
commit 91cfdaee72
200 changed files with 25589 additions and 0 deletions

137
src/main.py Normal file
View File

@@ -0,0 +1,137 @@
"""Agentic Microservice — FastAPI application entry point.
Wires together all components: Redis storage, model adapters, MCP client,
context engine, orchestrator, and SSE streaming.
"""
from __future__ import annotations
import logging
import pathlib
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles
from .adapters.claude_adapter import ClaudeAdapter
from .adapters.openai_adapter import OpenAIAdapter
from .api.routes import router, set_dependencies
from .config import settings
from .context.engine import ContextEngine
from .mcp.client import MCPClient
from .memory.store import MemoryStore
from .orchestrator.engine import OrchestratorEngine
from .storage.redis import RedisStorage
from .streaming.sse import SSEEmitter
logging.basicConfig(
level=logging.DEBUG if settings.debug else logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
# Global instances (initialized in lifespan)
redis_storage = RedisStorage()
mcp_client = MCPClient()
sse_emitter = SSEEmitter(redis_storage=redis_storage)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifecycle: startup and shutdown."""
logger.info("Starting %s v%s", settings.service_name, settings.service_version)
# 1. Connect Redis
await redis_storage.connect()
# Wire SSE emitter to Redis for event persistence (re-set after connect)
sse_emitter.set_storage(redis_storage)
# 2. Initialize model adapter (based on configured provider)
if settings.default_model_provider == "openai":
model_adapter = OpenAIAdapter()
logger.info("Using OpenAI adapter (model: %s)", settings.default_model_id)
else:
model_adapter = ClaudeAdapter()
logger.info("Using Claude adapter (model: %s)", settings.default_model_id)
# 3. Initialize memory store (uses same Redis connection)
memory_store = MemoryStore(redis_storage.client)
# 4. Initialize context engine (with memory store for knowledge base)
context_engine = ContextEngine(memory_store=memory_store)
# 5. Start MCP client (if configured)
if settings.mcp_server_command:
try:
await mcp_client.start()
logger.info("MCP client started with %d tools", len(mcp_client.tools))
except Exception as e:
logger.warning("MCP client failed to start: %s — continuing without MCP", e)
# 6. Initialize orchestrator
orchestrator = OrchestratorEngine(
model_adapter=model_adapter,
context_engine=context_engine,
mcp_client=mcp_client,
memory_store=memory_store,
sse_emitter=sse_emitter,
)
# 7. Wire dependencies into API routes
set_dependencies(
storage=redis_storage,
orchestrator=orchestrator,
sse_emitter=sse_emitter,
context_engine=context_engine,
memory_store=memory_store,
)
logger.info("All systems initialized. Serving on %s:%d", settings.host, settings.port)
yield
# Shutdown
logger.info("Shutting down...")
await mcp_client.stop()
await redis_storage.disconnect()
logger.info("Shutdown complete.")
app = FastAPI(
title=settings.service_name,
version=settings.service_version,
lifespan=lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Mount API routes
app.include_router(router, prefix="/api/v1")
# Health check
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok", "service": settings.service_name}
# Root redirect
@app.get("/")
async def root():
return RedirectResponse(url="/dashboard/")
# Dashboard static files (mounted AFTER API routes)
_dashboard_dir = pathlib.Path(__file__).resolve().parent.parent / "dashboard"
if _dashboard_dir.is_dir():
app.mount("/dashboard", StaticFiles(directory=str(_dashboard_dir), html=True), name="dashboard")