Spaces:
Running
Running
GitHub Actions commited on
Commit ·
3b6e5dc
1
Parent(s): a066e5a
Deploy from GitHub Actions (2026-03-12 18:14 UTC)
Browse files- app/main.py +8 -7
- app/models/conversation_manager.py +9 -10
- app/models/database.py +28 -80
- app/utils/config.py +3 -0
app/main.py
CHANGED
|
@@ -14,7 +14,7 @@ from pydantic import BaseModel
|
|
| 14 |
from app.utils.config import get_config, get_logger, get_version
|
| 15 |
from app.models.nlp_engine import NLPEngine
|
| 16 |
from app.models.conversation_manager import ConversationManager
|
| 17 |
-
from app.models.database import init_database,
|
| 18 |
from app.models.smart_models import UserPreference, UserInsight, ConversationTopic
|
| 19 |
from app.data.training_data import TRAINING_DATA
|
| 20 |
from app.routers import knowledge_base
|
|
@@ -34,13 +34,14 @@ async def lifespan(fastapi_app: FastAPI): # pylint: disable=unused-argument
|
|
| 34 |
|
| 35 |
# Initialize database
|
| 36 |
try:
|
| 37 |
-
|
| 38 |
-
|
|
|
|
| 39 |
logger.info("Database connection established successfully")
|
| 40 |
else:
|
| 41 |
logger.warning("Database health check failed, but continuing...")
|
| 42 |
-
except Exception as e:
|
| 43 |
-
logger.error("Database initialization failed: %s", str(e))
|
| 44 |
logger.warning("Continuing without database - some features may not work...")
|
| 45 |
|
| 46 |
# Train NLP model
|
|
@@ -101,7 +102,7 @@ async def health_check():
|
|
| 101 |
logger.debug("Health check endpoint accessed")
|
| 102 |
|
| 103 |
# Check database health
|
| 104 |
-
db_status = "healthy" if
|
| 105 |
|
| 106 |
return {
|
| 107 |
"status": "healthy",
|
|
@@ -190,7 +191,7 @@ async def chat(request: ChatRequest):
|
|
| 190 |
"threshold_applied": confidence < config.nlp["confidence_threshold"],
|
| 191 |
"environment": config.env.name,
|
| 192 |
"response_time_ms": response_time_ms,
|
| 193 |
-
"database_enabled":
|
| 194 |
}
|
| 195 |
|
| 196 |
logger.info("Chat response generated successfully for session %s", session_id)
|
|
|
|
| 14 |
from app.utils.config import get_config, get_logger, get_version
|
| 15 |
from app.models.nlp_engine import NLPEngine
|
| 16 |
from app.models.conversation_manager import ConversationManager
|
| 17 |
+
from app.models.database import init_database, health_check as db_health_check
|
| 18 |
from app.models.smart_models import UserPreference, UserInsight, ConversationTopic
|
| 19 |
from app.data.training_data import TRAINING_DATA
|
| 20 |
from app.routers import knowledge_base
|
|
|
|
| 34 |
|
| 35 |
# Initialize database
|
| 36 |
try:
|
| 37 |
+
init_database()
|
| 38 |
+
|
| 39 |
+
if db_health_check():
|
| 40 |
logger.info("Database connection established successfully")
|
| 41 |
else:
|
| 42 |
logger.warning("Database health check failed, but continuing...")
|
| 43 |
+
except Exception as e: # pylint: disable=broad-exception-caught
|
| 44 |
+
logger.error("Database initialization failed: %s: %s", type(e).__name__, str(e))
|
| 45 |
logger.warning("Continuing without database - some features may not work...")
|
| 46 |
|
| 47 |
# Train NLP model
|
|
|
|
| 102 |
logger.debug("Health check endpoint accessed")
|
| 103 |
|
| 104 |
# Check database health
|
| 105 |
+
db_status = "healthy" if db_health_check() else "unhealthy"
|
| 106 |
|
| 107 |
return {
|
| 108 |
"status": "healthy",
|
|
|
|
| 191 |
"threshold_applied": confidence < config.nlp["confidence_threshold"],
|
| 192 |
"environment": config.env.name,
|
| 193 |
"response_time_ms": response_time_ms,
|
| 194 |
+
"database_enabled": db_health_check(),
|
| 195 |
}
|
| 196 |
|
| 197 |
logger.info("Chat response generated successfully for session %s", session_id)
|
app/models/conversation_manager.py
CHANGED
|
@@ -8,7 +8,7 @@ from groq import Groq
|
|
| 8 |
|
| 9 |
# Absolute imports from project root
|
| 10 |
from app.utils.config import get_logger
|
| 11 |
-
from app.models.database import
|
| 12 |
from app.models.information_extractor import InformationExtractor
|
| 13 |
|
| 14 |
|
|
@@ -16,7 +16,6 @@ class ConversationManager:
|
|
| 16 |
def __init__(self, config=None):
|
| 17 |
self.config = config
|
| 18 |
self.logger = get_logger(__name__)
|
| 19 |
-
self.db = get_database()
|
| 20 |
self.info_extractor = InformationExtractor()
|
| 21 |
|
| 22 |
max_history = config.nlp["max_history"] if config else 50
|
|
@@ -28,7 +27,7 @@ class ConversationManager:
|
|
| 28 |
|
| 29 |
def get_or_create_user(self, session_id: str) -> User:
|
| 30 |
"""Get existing user or create new one"""
|
| 31 |
-
with
|
| 32 |
user = db_session.query(User).filter(User.session_id == session_id).first()
|
| 33 |
|
| 34 |
if not user:
|
|
@@ -46,7 +45,7 @@ class ConversationManager:
|
|
| 46 |
|
| 47 |
def get_active_conversation(self, user: User) -> Optional[Conversation]:
|
| 48 |
"""Get the most recent active conversation for a user"""
|
| 49 |
-
with
|
| 50 |
# Refresh user object in this session
|
| 51 |
user = db_session.merge(user)
|
| 52 |
|
|
@@ -81,7 +80,7 @@ class ConversationManager:
|
|
| 81 |
|
| 82 |
def create_conversation(self, user: User) -> Conversation:
|
| 83 |
"""Create a new conversation for the user"""
|
| 84 |
-
with
|
| 85 |
# Refresh user object in this session
|
| 86 |
user = db_session.merge(user)
|
| 87 |
|
|
@@ -97,7 +96,7 @@ class ConversationManager:
|
|
| 97 |
self, conversation: Conversation, limit: int = 5
|
| 98 |
) -> List[Dict]:
|
| 99 |
"""Get recent messages from conversation for context"""
|
| 100 |
-
with
|
| 101 |
# Refresh conversation object in this section
|
| 102 |
conversation = db_session.merge(conversation)
|
| 103 |
|
|
@@ -178,7 +177,7 @@ class ConversationManager:
|
|
| 178 |
|
| 179 |
# Update user's preferred language
|
| 180 |
if user and user.preferred_language != language:
|
| 181 |
-
with
|
| 182 |
user = db_session.merge(user)
|
| 183 |
user.preferred_language = language
|
| 184 |
db_session.commit()
|
|
@@ -233,7 +232,7 @@ class ConversationManager:
|
|
| 233 |
conversation = self.create_conversation(user)
|
| 234 |
|
| 235 |
# Create message record
|
| 236 |
-
with
|
| 237 |
# Refresh objects in this session
|
| 238 |
conversation = db_session.merge(conversation)
|
| 239 |
user = db_session.merge(user)
|
|
@@ -275,7 +274,7 @@ class ConversationManager:
|
|
| 275 |
def get_conversation_history(self, session_id: str) -> List[Dict]:
|
| 276 |
"""Get conversation history for a session (for analytics endpoint)"""
|
| 277 |
try:
|
| 278 |
-
with
|
| 279 |
user = (
|
| 280 |
db_session.query(User).filter(User.session_id == session_id).first()
|
| 281 |
)
|
|
@@ -316,7 +315,7 @@ class ConversationManager:
|
|
| 316 |
def get_user_stats(self, session_id: str) -> Dict:
|
| 317 |
"""Get user statistics"""
|
| 318 |
try:
|
| 319 |
-
with
|
| 320 |
user = (
|
| 321 |
db_session.query(User).filter(User.session_id == session_id).first()
|
| 322 |
)
|
|
|
|
| 8 |
|
| 9 |
# Absolute imports from project root
|
| 10 |
from app.utils.config import get_logger
|
| 11 |
+
from app.models.database import User, Conversation, Message, SessionLocal
|
| 12 |
from app.models.information_extractor import InformationExtractor
|
| 13 |
|
| 14 |
|
|
|
|
| 16 |
def __init__(self, config=None):
|
| 17 |
self.config = config
|
| 18 |
self.logger = get_logger(__name__)
|
|
|
|
| 19 |
self.info_extractor = InformationExtractor()
|
| 20 |
|
| 21 |
max_history = config.nlp["max_history"] if config else 50
|
|
|
|
| 27 |
|
| 28 |
def get_or_create_user(self, session_id: str) -> User:
|
| 29 |
"""Get existing user or create new one"""
|
| 30 |
+
with SessionLocal() as db_session:
|
| 31 |
user = db_session.query(User).filter(User.session_id == session_id).first()
|
| 32 |
|
| 33 |
if not user:
|
|
|
|
| 45 |
|
| 46 |
def get_active_conversation(self, user: User) -> Optional[Conversation]:
|
| 47 |
"""Get the most recent active conversation for a user"""
|
| 48 |
+
with SessionLocal() as db_session:
|
| 49 |
# Refresh user object in this session
|
| 50 |
user = db_session.merge(user)
|
| 51 |
|
|
|
|
| 80 |
|
| 81 |
def create_conversation(self, user: User) -> Conversation:
|
| 82 |
"""Create a new conversation for the user"""
|
| 83 |
+
with SessionLocal() as db_session:
|
| 84 |
# Refresh user object in this session
|
| 85 |
user = db_session.merge(user)
|
| 86 |
|
|
|
|
| 96 |
self, conversation: Conversation, limit: int = 5
|
| 97 |
) -> List[Dict]:
|
| 98 |
"""Get recent messages from conversation for context"""
|
| 99 |
+
with SessionLocal() as db_session:
|
| 100 |
# Refresh conversation object in this section
|
| 101 |
conversation = db_session.merge(conversation)
|
| 102 |
|
|
|
|
| 177 |
|
| 178 |
# Update user's preferred language
|
| 179 |
if user and user.preferred_language != language:
|
| 180 |
+
with SessionLocal() as db_session:
|
| 181 |
user = db_session.merge(user)
|
| 182 |
user.preferred_language = language
|
| 183 |
db_session.commit()
|
|
|
|
| 232 |
conversation = self.create_conversation(user)
|
| 233 |
|
| 234 |
# Create message record
|
| 235 |
+
with SessionLocal() as db_session:
|
| 236 |
# Refresh objects in this session
|
| 237 |
conversation = db_session.merge(conversation)
|
| 238 |
user = db_session.merge(user)
|
|
|
|
| 274 |
def get_conversation_history(self, session_id: str) -> List[Dict]:
|
| 275 |
"""Get conversation history for a session (for analytics endpoint)"""
|
| 276 |
try:
|
| 277 |
+
with SessionLocal() as db_session:
|
| 278 |
user = (
|
| 279 |
db_session.query(User).filter(User.session_id == session_id).first()
|
| 280 |
)
|
|
|
|
| 315 |
def get_user_stats(self, session_id: str) -> Dict:
|
| 316 |
"""Get user statistics"""
|
| 317 |
try:
|
| 318 |
+
with SessionLocal() as db_session:
|
| 319 |
user = (
|
| 320 |
db_session.query(User).filter(User.session_id == session_id).first()
|
| 321 |
)
|
app/models/database.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
# app/models/database.py
|
| 2 |
-
import os
|
| 3 |
from datetime import datetime, timezone
|
| 4 |
import uuid
|
| 5 |
from sqlalchemy import (
|
|
@@ -19,12 +18,23 @@ from sqlalchemy.orm import sessionmaker, relationship, declarative_base
|
|
| 19 |
from sqlalchemy.dialects.postgresql import UUID
|
| 20 |
|
| 21 |
# Absolute import from project root
|
| 22 |
-
from app.utils.config import get_logger
|
|
|
|
|
|
|
| 23 |
|
| 24 |
logger = get_logger(__name__)
|
| 25 |
|
| 26 |
Base = declarative_base()
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
class User(Base):
|
| 30 |
__tablename__ = "users"
|
|
@@ -105,90 +115,28 @@ class Message(Base):
|
|
| 105 |
return f"<Message(id={self.id}, intent={self.intent}, confidence={self.confidence})>"
|
| 106 |
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
if not self.database_url:
|
| 113 |
-
raise ValueError("DATABASE_URL environment variable is required")
|
| 114 |
-
|
| 115 |
-
# Create engine
|
| 116 |
-
self.engine = create_engine(
|
| 117 |
-
self.database_url,
|
| 118 |
-
echo=False, # Set to True for SQL debugging
|
| 119 |
-
pool_pre_ping=True, # Verify connections before use
|
| 120 |
-
pool_recycle=300, # Recycle connections every 5 minutes
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
# Create session factory
|
| 124 |
-
self.SessionLocal = sessionmaker(
|
| 125 |
-
autocommit=False, autoflush=False, bind=self.engine
|
| 126 |
-
)
|
| 127 |
-
|
| 128 |
-
logger.info(
|
| 129 |
-
"Database manager initialized with URL: %s",
|
| 130 |
-
self.database_url.split("@")[0] + "@***",
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
-
def create_tables(self):
|
| 134 |
-
"""Create all tables in the database"""
|
| 135 |
-
Base.metadata.create_all(bind=self.engine)
|
| 136 |
-
logger.info("Database tables created successfully")
|
| 137 |
-
|
| 138 |
-
def get_session(self):
|
| 139 |
-
"""Get a database session"""
|
| 140 |
-
return self.SessionLocal()
|
| 141 |
-
|
| 142 |
-
def health_check(self):
|
| 143 |
-
"""Check if database connection is working"""
|
| 144 |
-
try:
|
| 145 |
-
with self.get_session() as session:
|
| 146 |
-
from sqlalchemy import text
|
| 147 |
-
|
| 148 |
-
session.execute(text("SELECT 1"))
|
| 149 |
-
logger.info("Database health check passed")
|
| 150 |
-
return True
|
| 151 |
-
except Exception as e:
|
| 152 |
-
logger.error("Database health check failed: %s", str(e))
|
| 153 |
-
return False
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
class DatabaseManager:
|
| 157 |
-
"""Singleton database manager"""
|
| 158 |
-
|
| 159 |
-
def __init__(self):
|
| 160 |
-
self.db = None
|
| 161 |
-
|
| 162 |
-
def get_database(self) -> Database:
|
| 163 |
-
"""Get the database instance (create if doesn't exist)"""
|
| 164 |
-
if self.db is None:
|
| 165 |
-
self.db = Database()
|
| 166 |
-
return self.db
|
| 167 |
-
|
| 168 |
-
def init_database(self):
|
| 169 |
-
"""Initialize the database instance and create tables"""
|
| 170 |
-
db = self.get_database()
|
| 171 |
-
db.create_tables()
|
| 172 |
-
return db
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
# Create single instance of DatabaseManager
|
| 176 |
-
db_manager = DatabaseManager()
|
| 177 |
-
|
| 178 |
|
| 179 |
-
# Convenience functions for easier imports
|
| 180 |
-
def get_database() -> DatabaseManager:
|
| 181 |
-
"""Get the global database manager instance"""
|
| 182 |
-
return db_manager.get_database()
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
|
| 190 |
def get_db():
|
| 191 |
-
db =
|
| 192 |
try:
|
| 193 |
yield db
|
| 194 |
finally:
|
|
|
|
| 1 |
# app/models/database.py
|
|
|
|
| 2 |
from datetime import datetime, timezone
|
| 3 |
import uuid
|
| 4 |
from sqlalchemy import (
|
|
|
|
| 18 |
from sqlalchemy.dialects.postgresql import UUID
|
| 19 |
|
| 20 |
# Absolute import from project root
|
| 21 |
+
from app.utils.config import get_logger, config_manager
|
| 22 |
+
|
| 23 |
+
config = config_manager.get_config()
|
| 24 |
|
| 25 |
logger = get_logger(__name__)
|
| 26 |
|
| 27 |
Base = declarative_base()
|
| 28 |
|
| 29 |
+
engine = create_engine(
|
| 30 |
+
config.database_url,
|
| 31 |
+
echo=False, # Set to True for SQL debugging
|
| 32 |
+
pool_pre_ping=True, # Verify connections before use
|
| 33 |
+
pool_recycle=300, # Recycle connections every 5 minutes
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 37 |
+
|
| 38 |
|
| 39 |
class User(Base):
|
| 40 |
__tablename__ = "users"
|
|
|
|
| 115 |
return f"<Message(id={self.id}, intent={self.intent}, confidence={self.confidence})>"
|
| 116 |
|
| 117 |
|
| 118 |
+
def init_database():
|
| 119 |
+
# create all tables in the database
|
| 120 |
+
Base.metadata.create_all(bind=engine)
|
| 121 |
+
logger.info("Database tables created successfully")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
def health_check():
|
| 125 |
+
"""Check if database connection is working"""
|
| 126 |
+
try:
|
| 127 |
+
with SessionLocal() as session:
|
| 128 |
+
from sqlalchemy import text # pylint: disable=import-outside-toplevel
|
| 129 |
|
| 130 |
+
session.execute(text("SELECT 1"))
|
| 131 |
+
logger.info("Database health check passed")
|
| 132 |
+
return True
|
| 133 |
+
except Exception as e: # pylint: disable=broad-exception-caught
|
| 134 |
+
logger.error("Database health check failed: %s: %s", type(e).__name__, str(e))
|
| 135 |
+
return False
|
| 136 |
|
| 137 |
|
| 138 |
def get_db():
|
| 139 |
+
db = SessionLocal()
|
| 140 |
try:
|
| 141 |
yield db
|
| 142 |
finally:
|
app/utils/config.py
CHANGED
|
@@ -172,6 +172,9 @@ class Config:
|
|
| 172 |
self.llm_api_key = os.getenv("GROQ_API_KEY", "")
|
| 173 |
self.system_prompt = os.getenv("SYSTEM_PROMPT", "")
|
| 174 |
|
|
|
|
|
|
|
|
|
|
| 175 |
def _setup_logging(self):
|
| 176 |
"""Configure logging for the entire application"""
|
| 177 |
# Create logger
|
|
|
|
| 172 |
self.llm_api_key = os.getenv("GROQ_API_KEY", "")
|
| 173 |
self.system_prompt = os.getenv("SYSTEM_PROMPT", "")
|
| 174 |
|
| 175 |
+
# Database Configuration
|
| 176 |
+
self.database_url = os.getenv("DATABASE_URL", "")
|
| 177 |
+
|
| 178 |
def _setup_logging(self):
|
| 179 |
"""Configure logging for the entire application"""
|
| 180 |
# Create logger
|