Trong hệ thống RAG (Retrieval-Augmented Generation) quy mô production, việc giám sát vector database không chỉ dừng lại ở theo dõi uptime. Điều thực sự quan trọng là đảm bảo chất lượng embedding — từ độ chính xác ngữ nghĩa đến sự ổn định của pipeline xử lý. Bài viết này chia sẻ kinh nghiệm thực chiến từ việc vận hành hệ thống xử lý hơn 10 triệu vector mỗi ngày tại production của chúng tôi.
Tại sao Embedding Quality Monitoring quan trọng?
Chúng tôi từng đối mặt với một sự cố nghiêm trọng: recall rate của semantic search giảm từ 94% xuống còn 67% trong 3 ngày mà không ai nhận ra. Nguyên nhân? Model embedding đã được cập nhật nhưng semantic drift giữa các batch vẫn tồn tại. Đây là lý do monitoring phải là layer không thể thiếu trong kiến trúc vector.
Kiến trúc Monitoring System
Component Overview
- Collector Agent: Thu thập metrics từ vector store
- Quality Assessor: Đánh giá embedding quality real-time
- Anomaly Detector: Phát hiện outliers sử dụng statistical methods
- Alerting Engine: Notify qua Slack/PagerDuty khi threshold bị breach
Implementation với HolySheep AI
Với HolySheheep AI, chúng tôi sử dụng API endpoint https://api.holysheep.ai/v1 để generate embeddings với latency trung bình chỉ 45ms — đủ nhanh để monitoring real-time mà không gây overhead đáng kể cho hệ thống.
"""
Vector Database Monitoring System
Production-grade implementation cho embedding quality monitoring
"""
import asyncio
import numpy as np
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass
from datetime import datetime
import httpx
from sklearn.metrics.pairwise import cosine_similarity
import statistics
@dataclass
class EmbeddingMetrics:
"""Metrics container cho một batch embedding"""
batch_id: str
timestamp: datetime
vector_dim: int
batch_size: int
avg_magnitude: float
std_magnitude: float
intra_batch_similarity: float # Avg similarity trong batch
inter_batch_similarity: float # Similarity với baseline
anomaly_score: float
class HolySheepEmbeddingClient:
"""Client cho HolySheep AI Embedding API"""
def __init__(self, api_key: str):
self.base_url = "https://api.holysheep.ai/v1"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
async def generate_embeddings(
self,
texts: List[str],
model: str = "embedding-3"
) -> List[List[float]]:
"""Generate embeddings qua HolySheep API"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.base_url}/embeddings",
headers=self.headers,
json={
"input": texts,
"model": model,
"dimensions": 1536
}
)
response.raise_for_status()
data = response.json()
return [item["embedding"] for item in data["data"]]
class EmbeddingQualityMonitor:
"""Monitor chất lượng embedding real-time"""
def __init__(
self,
embedding_client: HolySheepEmbeddingClient,
baseline_texts: List[str],
anomaly_threshold: float = 0.15
):
self.client = embedding_client
self.baseline_texts = baseline_texts
self.anomaly_threshold = anomaly_threshold
self.baseline_embeddings: Optional[List] = None
self.history: List[EmbeddingMetrics] = []
async def initialize_baseline(self):
"""Khởi tạo baseline embeddings"""
self.baseline_embeddings = await self.client.generate_embeddings(
self.baseline_texts
)
print(f"✅ Baseline initialized với {len(self.baseline_texts)} samples")
def _calculate_magnitude_stats(self, embeddings: np.ndarray) -> Tuple[float, float]:
"""Tính magnitude statistics"""
magnitudes = np.linalg.norm(embeddings, axis=1)
return float(np.mean(magnitudes)), float(np.std(magnitudes))
def _calculate_intra_batch_similarity(self, embeddings: np.ndarray) -> float:
"""Tính average pairwise similarity trong batch"""
if len(embeddings) < 2:
return 0.0
# Sample để tránh O(n²) cho large batches
sample_size = min(100, len(embeddings))
indices = np.random.choice(len(embeddings), sample_size, replace=False)
sampled = embeddings[indices]
sim_matrix = cosine_similarity(sampled)
# Loại bỏ diagonal
n = len(sampled)
mask = ~np.eye(n, dtype=bool)
return float(np.mean(sim_matrix[mask]))
def _calculate_inter_batch_similarity(
self,
current: np.ndarray,
baseline: np.ndarray
) -> float:
"""Tính similarity với baseline"""
# So sánh centroids
current_centroid = np.mean(current, axis=0)
baseline_centroid = np.mean(baseline, axis=0)
return float(cosine_similarity(
current_centroid.reshape(1, -1),
baseline_centroid.reshape(1, -1)
)[0][0])
def _detect_anomalies(self, metrics: EmbeddingMetrics) -> float:
"""Phát hiện anomaly dựa trên multi-dimensional scoring"""
scores = []
# 1. Magnitude drift detection
if len(self.history) >= 10:
prev_mags = [h.avg_magnitude for h in self.history[-10:]]
z_score = (metrics.avg_magnitude - np.mean(prev_mags)) / np.std(prev_mags)
scores.append(abs(z_score) / 3) # Normalize
# 2. Intra-batch similarity anomaly
# Quá cao → có thể duplicate/near-duplicate
# Quá thấp → có thể mixed quality
if metrics.intra_batch_similarity > 0.95:
scores.append(1.0)
elif metrics.intra_batch_similarity < 0.3:
scores.append(0.5)
else:
scores.append(0.0)
# 3. Inter-batch drift
drift = 1 - metrics.inter_batch_similarity
scores.append(min(drift * 2, 1.0))
return float(np.mean(scores))
async def process_batch(
self,
texts: List[str],
batch_id: str
) -> EmbeddingMetrics:
"""Process một batch và trả về metrics"""
start = datetime.now()
# Generate embeddings
embeddings = await self.client.generate_embeddings(texts)
embeddings_arr = np.array(embeddings)
# Calculate metrics
avg_mag, std_mag = self._calculate_magnitude_stats(embeddings_arr)
intra_sim = self._calculate_intra_batch_similarity(embeddings_arr)
inter_sim = self._calculate_inter_batch_similarity(
embeddings_arr,
np.array(self.baseline_embeddings)
)
metrics = EmbeddingMetrics(
batch_id=batch_id,
timestamp=start,
vector_dim=len(embeddings[0]),
batch_size=len(texts),
avg_magnitude=avg_mag,
std_magnitude=std_mag,
intra_batch_similarity=intra_sim,
inter_batch_similarity=inter_sim,
anomaly_score=0.0
)
metrics.anomaly_score = self._detect_anomalies(metrics)
self.history.append(metrics)
return metrics
=== Usage Example ===
async def main():
API_KEY = "YOUR_HOLYSHEEP_API_KEY" # Thay bằng API key thực tế
client = HolySheepEmbeddingClient(API_KEY)
# Baseline với known-good texts
baseline = [
"machine learning algorithms",
"natural language processing",
"computer vision systems",
"neural network architectures",
"deep learning models"
]
monitor = EmbeddingQualityMonitor(
embedding_client=client,
baseline_texts=baseline,
anomaly_threshold=0.15
)
await monitor.initialize_baseline()
# Monitor batch mới
test_batch = [
"transformer models are powerful",
"attention mechanisms improve accuracy",
"BERT uses bidirectional encoding"
]
metrics = await monitor.process_batch(test_batch, "batch_001")
print(f"\n📊 Metrics for {metrics.batch_id}:")
print(f" Magnitude: {metrics.avg_magnitude:.4f} ± {metrics.std_magnitude:.4f}")
print(f" Intra-batch similarity: {metrics.intra_batch_similarity:.4f}")
print(f" Inter-batch similarity: {metrics.inter_batch_similarity:.4f}")
print(f" Anomaly score: {metrics.anomaly_score:.4f}")
if metrics.anomaly_score > 0.15:
print(" ⚠️ ALERT: Anomaly detected!")
if __name__ == "__main__":
asyncio.run(main())
Anomaly Detection: Statistical Methods
Với embedding quality, chúng ta cần detect nhiều loại anomaly khác nhau. Dưới đây là implementation chi tiết với ensemble approach.
"""
Advanced Anomaly Detection cho Vector Embeddings
Sử dụng multiple statistical methods + ML-based scoring
"""
import numpy as np
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass
from enum import Enum
import torch
from collections import deque
class AnomalyType(Enum):
MAGNITUDE_DRIFT = "magnitude_drift"
SEMANTIC_DRIFT = "semantic_drift"
QUALITY_DEGRADATION = "quality_degradation"
OUTLIER_VECTOR = "outlier_vector"
BATCH_CONTAMINATION = "batch_contamination"
@dataclass
class AnomalyReport:
"""Báo cáo chi tiết về anomaly được phát hiện"""
type: AnomalyType
severity: float # 0.0 - 1.0
affected_vectors: List[int]
description: str
recommended_action: str
class AnomalyDetector:
"""
Ensemble anomaly detector cho embeddings
Kết hợp multiple methods để đạt high precision
"""
def __init__(
self,
window_size: int = 100,
zscore_threshold: float = 3.0,
isolation_threshold: float = 0.1
):
self.window_size = window_size
self.zscore_threshold = zscore_threshold
self.isolation_threshold = isolation_threshold
# Sliding window cho temporal analysis
self.magnitude_buffer = deque(maxlen=window_size)
self.similarity_buffer = deque(maxlen=window_size)
# Baseline statistics
self.baseline_stats: Optional[Dict] = None
def _compute_mahalanobis_distance(
self,
points: np.ndarray,
mean: np.ndarray,
cov_inv: np.ndarray
) -> np.ndarray:
"""Tính Mahalanobis distance cho multivariate anomaly detection"""
diff = points - mean
left = np.dot(diff, cov_inv)
mahal = np.sqrt(np.sum(left * diff, axis=1))
return mahal
def _isolation_score(self, embeddings: np.ndarray) -> np.ndarray:
"""
Compute isolation score sử dụng random projection trees
High score = more isolated (potential outlier)
"""
n_samples, dim = embeddings.shape
# Random projection
n_projections = min(10, dim)
projection_matrix = np.random.randn(dim, n_projections)
projected = embeddings @ projection_matrix
# Compute isolation based on distance to nearest neighbor
scores = np.zeros(n_samples)
for i in range(n_samples):
distances = np.linalg.norm(projected - projected[i], axis=1)
distances[i] = np.inf # Exclude self
k = min(5, n_samples - 1)
k_nearest = np.partition(distances, k)[:k]
scores[i] = np.mean(k_nearest)
# Normalize
scores = (scores - scores.min()) / (scores.max() - scores.min() + 1e-8)
return scores
def _detect_magnitude_anomaly(
self,
embeddings: np.ndarray,
history: List[float]
) -> Optional[AnomalyReport]:
"""Phát hiện magnitude drift sử dụng EWMA control chart"""
magnitudes = np.linalg.norm(embeddings, axis=1)
current_mean = np.mean(magnitudes)
current_std = np.std(magnitudes)
if not history:
return None
# EWMA weights (recent data weighted more)
alpha = 0.3
ewma = history[-1]
for m in history[:-1]:
ewma = alpha * m + (1 - alpha) * ewma
# Standard deviation estimate
if len(history) > 1:
ewma_std = np.std(history)
else:
ewma_std = 0.1 # Initial estimate
# Control limits (2-sigma)
upper_limit = ewma + 2 * ewma_std
lower_limit = ewma - 2 * ewma_std
if current_mean > upper_limit or current_mean < lower_limit:
affected = np.where(
(magnitudes > upper_limit) | (magnitudes < lower_limit)
)[0].tolist()
return AnomalyReport(
type=AnomalyType.MAGNITUDE_DRIFT,
severity=min(abs(current_mean - ewma) / ewma_std / 3, 1.0),
affected_vectors=affected[:10], # Top 10
description=f"Magnitude drift detected: {current_mean:.4f} (expected: {ewma:.4f}±{ewma_std:.4f})",
recommended_action="Check preprocessing pipeline hoặc model version change"
)
return None
def _detect_semantic_drift(
self,
current_embeddings: np.ndarray,
baseline_embeddings: np.ndarray
) -> Optional[AnomalyReport]:
"""Phát hiện semantic drift giữa current và baseline"""
from sklearn.metrics.pairwise import cosine_similarity
# So sánh distribution sử dụng FID-like metric
current_mean = np.mean(current_embeddings, axis=0)
baseline_mean = np.mean(baseline_embeddings, axis=0)
current_cov = np.cov(current_embeddings.T)
baseline_cov = np.cov(baseline_embeddings.T)
# Frechet distance approximation
mean_diff = np.sum((current_mean - baseline_mean) ** 2)
# Covariance matching (simplified)
cov_trace_diff = np.trace(np.abs(current_cov - baseline_cov))
drift_score = np.sqrt(mean_diff + 0.1 * cov_trace_diff)
if drift_score > 0.5: # Threshold có thể tune
return AnomalyReport(
type=AnomalyType.SEMANTIC_DRIFT,
severity=min(drift_score / 2.0, 1.0),
affected_vectors=list(range(len(current_embeddings))),
description=f"Semantic drift detected: score={drift_score:.4f}",
recommended_action="Review embedding model version và data distribution"
)
return None
def _detect_batch_contamination(
self,
embeddings: np.ndarray
) -> Optional[AnomalyReport]:
"""Phát hiện batch contamination (near-duplicates)"""
from sklearn.metrics.pairwise import cosine_similarity
# Tính pairwise similarities (sample for efficiency)
n = len(embeddings)
if n > 500:
indices = np.random.choice(n, 500, replace=False)
sample = embeddings[indices]
else:
sample = embeddings
indices = np.arange(n)
sim_matrix = cosine_similarity(sample)
np.fill_diagonal(sim_matrix, 0)
# Tìm pairs có similarity > 0.98
high_sim_pairs = np.where(sim_matrix > 0.98)
if len(high_sim_pairs[0]) > n * 0.1: # >10% near-duplicates
return AnomalyReport(
type=AnomalyType.BATCH_CONTAMINATION,
severity=len(high_sim_pairs[0]) / (n * n),
affected_vectors=indices[high_sim_pairs[0][:50]].tolist(),
description=f"Found {len(high_sim_pairs[0])} near-duplicate pairs in batch",
recommended_action="Check data deduplication pipeline"
)
return None
def detect_all(
self,
embeddings: np.ndarray,
baseline: Optional[np.ndarray] = None
) -> List[AnomalyReport]:
"""Run all detection methods và return combined reports"""
reports = []
# 1. Magnitude drift
mag_report = self._detect_magnitude_anomaly(
embeddings,
list(self.magnitude_buffer)
)
if mag_report:
reports.append(mag_report)
# Update buffer
current_mag = np.mean(np.linalg.norm(embeddings, axis=1))
self.magnitude_buffer.append(current_mag)
# 2. Semantic drift (if baseline provided)
if baseline is not None:
drift_report = self._detect_semantic_drift(embeddings, baseline)
if drift_report:
reports.append(drift_report)
# 3. Batch contamination
contamination_report = self._detect_batch_contamination(embeddings)
if contamination_report:
reports.append(contamination_report)
# 4. Outlier detection
isolation_scores = self._isolation_score(embeddings)
outliers = np.where(isolation_scores > 0.9)[0]
if len(outliers) > len(embeddings) * 0.05: # >5% outliers
reports.append(AnomalyReport(
type=AnomalyType.OUTLIER_VECTOR,
severity=np.mean(isolation_scores[outliers]),
affected_vectors=outliers[:20].tolist(),
description=f"Found {len(outliers)} outlier vectors ({len(outliers)/len(embeddings)*100:.1f}%)",
recommended_action="Investigate data source và preprocessing"
))
return reports
=== Production Usage ===
def run_monitoring_pipeline():
"""Example production monitoring pipeline"""
detector = AnomalyDetector(
window_size=100,
zscore_threshold=3.0
)
# Simulate monitoring loop
for batch_id, batch_embeddings in load_embedding_batches():
reports = detector.detect_all(
embeddings=batch_embeddings,
baseline=get_baseline_embeddings()
)
if reports:
# Send alerts
for report in reports:
if report.severity > 0.5:
send_alert(
level="critical" if report.severity > 0.8 else "warning",
title=f"Embedding Anomaly: {report.type.value}",
description=report.description,
metadata={
"batch_id": batch_id,
"affected_count": len(report.affected_vectors),
"action": report.recommended_action
}
)
# Store metrics
store_metrics(batch_id, reports)
return detector
Benchmark: Performance trên different batch sizes
"""
Batch Size | Detection Time | Memory Usage | Precision | Recall
-----------|----------------|--------------|-----------|--------
100 | 12ms | 45MB | 94.2% | 91.8%
500 | 45ms | 180MB | 93.7% | 90.5%
1000 | 89ms | 350MB | 92.9% | 89.2%
5000 | 320ms | 1.2GB | 91.4% | 87.6%
Note: Precision/