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.app = app
|
||||||
self.included_routers = []
|
self.included_routers = []
|
||||||
self.failed_routers = []
|
self.failed_routers = []
|
||||||
|
self.skipped_routers = []
|
||||||
|
|
||||||
def _is_verbose(self) -> bool:
|
def _is_verbose(self) -> bool:
|
||||||
return os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
return os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||||
@@ -127,6 +128,10 @@ class RouterManager:
|
|||||||
|
|
||||||
for entry in registry:
|
for entry in registry:
|
||||||
if not self._should_include_router(entry, profile):
|
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
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -156,10 +161,30 @@ class RouterManager:
|
|||||||
"active_profile": self._get_profile(),
|
"active_profile": self._get_profile(),
|
||||||
"included_routers": self.included_routers,
|
"included_routers": self.included_routers,
|
||||||
"failed_routers": self.failed_routers,
|
"failed_routers": self.failed_routers,
|
||||||
|
"skipped_routers": self.skipped_routers,
|
||||||
"total_included": len(self.included_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]:
|
def get_feature_profile_status(self) -> Dict[str, Any]:
|
||||||
"""Get feature profile status and enabled modules."""
|
"""Get feature profile status and enabled modules."""
|
||||||
profile = self._get_profile()
|
profile = self._get_profile()
|
||||||
|
|||||||
@@ -328,6 +328,9 @@ else:
|
|||||||
"reason": "Full mode",
|
"reason": "Full mode",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Log startup summary
|
||||||
|
router_manager.log_startup_summary()
|
||||||
|
|
||||||
# Safety net: keep subscription routes available even if core inclusion flow changes
|
# 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.
|
# in special modes (e.g., demo mode). De-dup is handled by RouterManager.
|
||||||
router_manager.include_router_safely(subscription_router, "subscription")
|
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 os
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
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"}
|
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"}
|
return profile not in {"podcast", "youtube", "planning"}
|
||||||
|
|
||||||
|
|
||||||
def bootstrap_linguistic_models():
|
def bootstrap_linguistic_models() -> BootstrapResult:
|
||||||
"""
|
"""
|
||||||
Bootstrap spaCy and NLTK models BEFORE any imports.
|
Bootstrap spaCy and NLTK models BEFORE any imports.
|
||||||
This prevents import-time failures when EnhancedLinguisticAnalyzer is loaded.
|
This prevents import-time failures when EnhancedLinguisticAnalyzer is loaded.
|
||||||
@@ -85,7 +97,7 @@ def bootstrap_linguistic_models():
|
|||||||
if verbose:
|
if verbose:
|
||||||
print(f" ❌ Failed to download spaCy model: {e}")
|
print(f" ❌ Failed to download spaCy model: {e}")
|
||||||
print(" Please run: python -m spacy download en_core_web_sm")
|
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:
|
except ImportError:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" ⚠️ spaCy not installed - skipping")
|
print(" ⚠️ spaCy not installed - skipping")
|
||||||
@@ -114,7 +126,6 @@ def bootstrap_linguistic_models():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f" ⚠️ Failed to download {data_package}: {e}")
|
print(f" ⚠️ Failed to download {data_package}: {e}")
|
||||||
# Try fallback
|
|
||||||
if data_package == 'punkt_tab':
|
if data_package == 'punkt_tab':
|
||||||
try:
|
try:
|
||||||
nltk.download('punkt', quiet=True)
|
nltk.download('punkt', quiet=True)
|
||||||
@@ -128,10 +139,10 @@ def bootstrap_linguistic_models():
|
|||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print("✅ Linguistic model bootstrap complete")
|
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.
|
Bootstrap Local LLM models (Qwen) for SIF Agents.
|
||||||
This ensures the model is cached locally before the server starts,
|
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 os.getenv("RENDER") or os.getenv("RAILWAY_ENVIRONMENT"):
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" ⚠️ Cloud environment detected (Render/Railway). Skipping local LLM bootstrap to save RAM/Time.")
|
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"
|
target_model = "Qwen/Qwen2.5-3B-Instruct"
|
||||||
|
|
||||||
@@ -176,29 +187,52 @@ def bootstrap_local_llm_models():
|
|||||||
if verbose:
|
if verbose:
|
||||||
print(f" ⚠️ Failed to download/check local LLM: {e}")
|
print(f" ⚠️ Failed to download/check local LLM: {e}")
|
||||||
print(" SIF agents may try to download it at runtime.")
|
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:
|
except ImportError:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(" ⚠️ huggingface_hub not installed - skipping LLM bootstrap")
|
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 linguistic models BEFORE any imports that might need them
|
||||||
|
BOOTSTRAP_RESULTS = []
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
profile = get_active_profile()
|
||||||
|
os.environ["ALWRITY_ACTIVE_PROFILE"] = profile
|
||||||
|
|
||||||
|
print(f"\n📋 Active profile: {profile}")
|
||||||
|
|
||||||
if should_bootstrap_linguistic_models():
|
if should_bootstrap_linguistic_models():
|
||||||
bootstrap_linguistic_models()
|
result = bootstrap_linguistic_models()
|
||||||
|
BOOTSTRAP_RESULTS.append(result)
|
||||||
else:
|
else:
|
||||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||||
if verbose:
|
if verbose:
|
||||||
print("⏭️ Skipping linguistic model bootstrap (profile-gated)")
|
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():
|
if should_bootstrap_local_llm_models():
|
||||||
bootstrap_local_llm_models()
|
result = bootstrap_local_llm_models()
|
||||||
|
BOOTSTRAP_RESULTS.append(result)
|
||||||
else:
|
else:
|
||||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||||
if verbose:
|
if verbose:
|
||||||
print("⏭️ Skipping local LLM model bootstrap (profile-gated)")
|
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)
|
# NOW import modular utilities (after bootstrap)
|
||||||
from alwrity_utils import (
|
from alwrity_utils import (
|
||||||
|
|||||||
Reference in New Issue
Block a user