feat: Complete all 4 major optimization tasks

 Network & Model Optimization:
- Fixed Sentence Transformers path to use local model
- Configured real semantic embeddings (384-dimensional)
- Replaced hash-based fallback with AI-powered similarity

 Advanced AI Features Integration:
- Added ai_analyzer.py with Groq LLM integration
- Implemented article summarization, sentiment analysis, keyword extraction
- Added AI endpoints: /analyze-article, /generate-insights, /ai-status

 API Enhancement & User Experience:
- Enhanced articles endpoint with pagination (offset/limit, metadata)
- Added advanced filtering (date ranges, source, category)
- Improved search with semantic similarity + multi-parameter filters

 Production Polish & Performance:
- Implemented in-memory caching system in vector_store.py
- Added rate limiting (100 req/min per IP)
- Enhanced API documentation with deployment guide
- Fixed file structure compliance

System now production-ready with 1000+ articles indexed and full AI capabilities.
This commit is contained in:
Aherobo Ovie Victor
2025-07-08 16:45:38 +01:00
parent 3c4a08d639
commit beed04d05c
8 changed files with 789 additions and 65 deletions
+67 -36
View File
@@ -23,37 +23,49 @@ class EmbeddingGenerator:
self.cohere_client = None
self.sentence_model = None
self.use_cohere = COHERE_AVAILABLE and bool(settings.cohere_api_key)
self.use_sentence_transformers = SENTENCE_TRANSFORMERS_AVAILABLE
self.model_loaded = False
self.dimension = settings.vector_dimension
self.embedding_method = "hash" # Default fallback
# Initialize embedding model
if self.use_cohere:
# Priority: 1. Local Sentence Transformers, 2. Cohere, 3. Hash fallback
# Use lazy loading for faster startup
if self.use_sentence_transformers:
print("🚀 Sentence Transformers available - will load on first use")
self.embedding_method = "sentence_transformers"
self.model_loaded = True # Mark as ready for lazy loading
if not self.use_sentence_transformers and self.use_cohere:
try:
self.cohere_client = cohere.Client(settings.cohere_api_key)
self.embedding_method = "cohere"
print("✅ Using Cohere for embeddings")
self.model_loaded = True
except Exception as e:
print(f"❌ Cohere initialization failed: {e}")
self.use_cohere = False
if not self.use_cohere:
# Always start with simple embeddings for immediate functionality
print("⚡ Using fast hash-based embeddings for immediate startup")
self.model_loaded = True # Simple embeddings are always ready
# Note: Sentence Transformers available for future enhancement
if not self.use_sentence_transformers and not self.use_cohere:
print("⚡ Using enhanced hash-based embeddings as fallback")
self.embedding_method = "hash"
self.model_loaded = True
def _load_sentence_model(self):
"""Lazy load sentence transformer model"""
if not self.model_loaded and SENTENCE_TRANSFORMERS_AVAILABLE:
"""Lazy load sentence transformer model on first use"""
if self.sentence_model is None and self.use_sentence_transformers:
try:
print("📥 Loading Sentence Transformer model (this may take a moment)...")
print("📥 Loading local Sentence Transformers model (first use)...")
self.sentence_model = SentenceTransformer(settings.embedding_model)
self.model_loaded = True
print("✅ Sentence Transformer model loaded successfully")
print("✅ Local Sentence Transformers loaded successfully!")
print(f"📊 Model dimension: {self.sentence_model.get_sentence_embedding_dimension()}")
return True
except Exception as e:
print(f"❌ Failed to load Sentence Transformer: {e}")
self.sentence_model = None
self.model_loaded = False
print(f"❌ Failed to load local Sentence Transformers: {e}")
print("⚡ Falling back to hash-based embeddings")
self.use_sentence_transformers = False
self.embedding_method = "hash"
return False
return self.sentence_model is not None
def _simple_text_to_vector(self, text: str) -> np.ndarray:
"""Convert text to a simple vector using basic hashing (fallback method)"""
@@ -125,26 +137,47 @@ class EmbeddingGenerator:
return np.array(embeddings)
def generate_embeddings(self, articles: List[Dict[str, Any]]) -> np.ndarray:
"""Generate embeddings for articles"""
"""Generate embeddings for articles using best available method"""
if not articles:
return np.array([])
# Create texts for embedding
texts = [self.create_article_text(article) for article in articles]
print(f"Generating embeddings for {len(texts)} articles...")
# Generate embeddings
if self.use_cohere:
print(f"🔄 Generating embeddings for {len(texts)} articles using {self.embedding_method}...")
# Priority: Sentence Transformers > Cohere > Hash fallback
if self.use_sentence_transformers:
# Lazy load model on first use
if self._load_sentence_model():
embeddings = self.generate_embeddings_sentence_transformer(texts)
else:
# Fallback to hash if model loading failed
embeddings = np.array([self._simple_text_to_vector(text) for text in texts])
elif self.use_cohere:
embeddings = self.generate_embeddings_cohere(texts)
else:
embeddings = self.generate_embeddings_sentence_transformer(texts)
print(f"Generated embeddings shape: {embeddings.shape}")
# Enhanced hash-based fallback
embeddings = np.array([self._simple_text_to_vector(text) for text in texts])
print(f"✅ Generated embeddings shape: {embeddings.shape}")
return embeddings
def generate_query_embedding(self, query: str) -> np.ndarray:
"""Generate embedding for a search query"""
"""Generate embedding for a search query using best available method"""
print(f"🔍 Generating query embedding using {self.embedding_method}...")
# Priority: Sentence Transformers > Cohere > Hash fallback
if self.use_sentence_transformers:
# Lazy load model on first use
if self._load_sentence_model():
try:
embedding = self.sentence_model.encode([query], convert_to_numpy=True)[0]
print(f"✅ Query embedding generated with shape: {embedding.shape}")
return embedding
except Exception as e:
print(f"❌ Sentence Transformers query error: {e}")
if self.use_cohere:
try:
response = self.cohere_client.embed(
@@ -152,17 +185,15 @@ class EmbeddingGenerator:
model='embed-english-v3.0',
input_type='search_query'
)
return np.array(response.embeddings[0])
embedding = np.array(response.embeddings[0])
print(f"✅ Query embedding generated with shape: {embedding.shape}")
return embedding
except Exception as e:
print(f"Cohere query embedding error: {e}")
# Fallback to simple embeddings
return self._simple_text_to_vector(query)
else:
if self.sentence_model is not None:
return self.sentence_model.encode([query], convert_to_numpy=True)[0]
else:
# Use simple hash-based embeddings
return self._simple_text_to_vector(query)
print(f"Cohere query embedding error: {e}")
# Fallback to hash-based embeddings
print("⚡ Using hash-based fallback for query embedding")
return self._simple_text_to_vector(query)
def compute_similarity(self, embedding1: np.ndarray, embedding2: np.ndarray) -> float:
"""Compute cosine similarity between two embeddings"""