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:
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user