Initial commit
This commit is contained in:
137
src/main.py
Normal file
137
src/main.py
Normal 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")
|
||||
Reference in New Issue
Block a user