Add startup summary for active profile, routers, and bootstraps

- Add BootstrapResult dataclass for structured bootstrap results
- bootstrap_linguistic_models() and bootstrap_local_llm_models() return BootstrapResult
- Set ALWRITY_ACTIVE_PROFILE env var at startup and print active profile
- Set ALWRITY_BOOTSTRAP_SUMMARY with JSON summary of bootstrap results
- Print bootstrap summary at startup
- Track skipped_routers in RouterManager with reasons
- Add log_startup_summary() to log enabled/skipped/failed routers
- Call log_startup_summary() in app.py after router inclusion
This commit is contained in:
ajaysi
2026-03-31 15:23:41 +05:30
parent 08e51f76fa
commit bf6cdf1109
3 changed files with 73 additions and 11 deletions

View File

@@ -79,6 +79,7 @@ class RouterManager:
self.app = app
self.included_routers = []
self.failed_routers = []
self.skipped_routers = []
def _is_verbose(self) -> bool:
return os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
@@ -127,6 +128,10 @@ class RouterManager:
for entry in registry:
if not self._should_include_router(entry, profile):
reason = f"profile '{profile}' not in {entry.get('profiles', set())}"
self.skipped_routers.append({"name": entry["name"], "reason": reason})
if verbose:
logger.info(f"⏭️ Skipping {entry['name']}: {reason}")
continue
try:
@@ -156,10 +161,30 @@ class RouterManager:
"active_profile": self._get_profile(),
"included_routers": self.included_routers,
"failed_routers": self.failed_routers,
"skipped_routers": self.skipped_routers,
"total_included": len(self.included_routers),
"total_failed": len(self.failed_routers)
"total_failed": len(self.failed_routers),
"total_skipped": len(self.skipped_routers)
}
def log_startup_summary(self) -> None:
"""Log startup summary including profile, enabled routers, and skipped items."""
profile = self._get_profile()
logger.info("=" * 60)
logger.info("📋 STARTUP SUMMARY")
logger.info(f" Active profile: {profile}")
logger.info(f" Enabled routers ({len(self.included_routers)}): {', '.join(self.included_routers)}")
if self.skipped_routers:
logger.info(f" Skipped routers ({len(self.skipped_routers)}):")
for s in self.skipped_routers:
logger.info(f" - {s['name']}: {s['reason']}")
if self.failed_routers:
logger.warning(f" Failed routers ({len(self.failed_routers)}):")
for f in self.failed_routers:
logger.warning(f" - {f['name']}: {f['error']}")
logger.info("=" * 60)
def get_feature_profile_status(self) -> Dict[str, Any]:
"""Get feature profile status and enabled modules."""
profile = self._get_profile()

View File

@@ -328,6 +328,9 @@ else:
"reason": "Full mode",
}
# Log startup summary
router_manager.log_startup_summary()
# Safety net: keep subscription routes available even if core inclusion flow changes
# in special modes (e.g., demo mode). De-dup is handled by RouterManager.
router_manager.include_router_safely(subscription_router, "subscription")

View File

@@ -7,8 +7,20 @@ Run this from the backend directory to set up and start the FastAPI server.
import os
import sys
import json
import argparse
from pathlib import Path
from dataclasses import dataclass, asdict
from typing import Optional
@dataclass
class BootstrapResult:
name: str
success: bool
skipped: bool
reason: Optional[str] = None
details: Optional[str] = None
LINGUISTIC_REQUIRED_FEATURES = {"content_planning", "strategy_copilot", "facebook", "linkedin", "blog_writer", "persona"}
@@ -52,7 +64,7 @@ def should_bootstrap_local_llm_models() -> bool:
return profile not in {"podcast", "youtube", "planning"}
def bootstrap_linguistic_models():
def bootstrap_linguistic_models() -> BootstrapResult:
"""
Bootstrap spaCy and NLTK models BEFORE any imports.
This prevents import-time failures when EnhancedLinguisticAnalyzer is loaded.
@@ -85,7 +97,7 @@ def bootstrap_linguistic_models():
if verbose:
print(f" ❌ Failed to download spaCy model: {e}")
print(" Please run: python -m spacy download en_core_web_sm")
return False
return BootstrapResult(name="linguistic_models", success=False, skipped=False, reason="spacy_download_failed")
except ImportError:
if verbose:
print(" ⚠️ spaCy not installed - skipping")
@@ -114,7 +126,6 @@ def bootstrap_linguistic_models():
except Exception as e:
if verbose:
print(f" ⚠️ Failed to download {data_package}: {e}")
# Try fallback
if data_package == 'punkt_tab':
try:
nltk.download('punkt', quiet=True)
@@ -128,10 +139,10 @@ def bootstrap_linguistic_models():
if verbose:
print("✅ Linguistic model bootstrap complete")
return True
return BootstrapResult(name="linguistic_models", success=True, skipped=False)
def bootstrap_local_llm_models():
def bootstrap_local_llm_models() -> BootstrapResult:
"""
Bootstrap Local LLM models (Qwen) for SIF Agents.
This ensures the model is cached locally before the server starts,
@@ -158,7 +169,7 @@ def bootstrap_local_llm_models():
if os.getenv("RENDER") or os.getenv("RAILWAY_ENVIRONMENT"):
if verbose:
print(" ⚠️ Cloud environment detected (Render/Railway). Skipping local LLM bootstrap to save RAM/Time.")
return True
return BootstrapResult(name="local_llm_models", success=True, skipped=True, reason="cloud_environment")
target_model = "Qwen/Qwen2.5-3B-Instruct"
@@ -176,29 +187,52 @@ def bootstrap_local_llm_models():
if verbose:
print(f" ⚠️ Failed to download/check local LLM: {e}")
print(" SIF agents may try to download it at runtime.")
return False
return BootstrapResult(name="local_llm_models", success=False, skipped=False, reason=str(e))
except ImportError:
if verbose:
print(" ⚠️ huggingface_hub not installed - skipping LLM bootstrap")
return BootstrapResult(name="local_llm_models", success=False, skipped=True, reason="huggingface_hub_not_installed")
return True
return BootstrapResult(name="local_llm_models", success=True, skipped=False)
# Bootstrap linguistic models BEFORE any imports that might need them
BOOTSTRAP_RESULTS = []
if __name__ == "__main__":
profile = get_active_profile()
os.environ["ALWRITY_ACTIVE_PROFILE"] = profile
print(f"\n📋 Active profile: {profile}")
if should_bootstrap_linguistic_models():
bootstrap_linguistic_models()
result = bootstrap_linguistic_models()
BOOTSTRAP_RESULTS.append(result)
else:
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("⏭️ Skipping linguistic model bootstrap (profile-gated)")
BOOTSTRAP_RESULTS.append(BootstrapResult(name="linguistic_models", success=True, skipped=True, reason="profile_gated"))
if should_bootstrap_local_llm_models():
bootstrap_local_llm_models()
result = bootstrap_local_llm_models()
BOOTSTRAP_RESULTS.append(result)
else:
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("⏭️ Skipping local LLM model bootstrap (profile-gated)")
BOOTSTRAP_RESULTS.append(BootstrapResult(name="local_llm_models", success=True, skipped=True, reason="profile_gated"))
summary = {
"active_profile": profile,
"bootstraps": [asdict(r) for r in BOOTSTRAP_RESULTS]
}
os.environ["ALWRITY_BOOTSTRAP_SUMMARY"] = json.dumps(summary)
print(f"\n📋 Bootstrap Summary:")
for r in BOOTSTRAP_RESULTS:
status = "⏭️ Skipped" if r.skipped else ("✅ Enabled" if r.success else "❌ Failed")
print(f" {r.name}: {status}" + (f" ({r.reason})" if r.reason else ""))
# NOW import modular utilities (after bootstrap)
from alwrity_utils import (