Spaces:
Runtime error
Runtime error
feat: synchronize text-to-sql-bot codebase with Hugging Face Space repository, including Docker build configurations
6086e71 | """ | |
| ML Intent Classifier β Sentence-transformer embeddings + LogisticRegression. | |
| Provides a trained ML model for intent classification, replacing pure heuristic | |
| keyword matching with learned representations. Falls back gracefully when the | |
| model file is not available. | |
| Architecture: | |
| sentence-transformers/all-MiniLM-L6-v2 (384-dim) β LogisticRegression (4 classes) | |
| Classes: | |
| chat β Greetings, thanks, capability questions, off-topic | |
| sql β Data queries, aggregations, comparisons, joins | |
| ambiguous β Vague data-shaped queries that need clarification | |
| meta_query β Schema exploration (show tables, describe columns) | |
| """ | |
| import os | |
| import structlog | |
| from typing import Optional | |
| from dataclasses import dataclass | |
| logger = structlog.get_logger() | |
| # Path to the pre-trained model artifact | |
| _MODEL_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| _MODEL_PATH = os.path.join(_MODEL_DIR, "models", "intent_model.joblib") | |
| # Map ML labels to route_intent values for pipeline compatibility | |
| _ROUTE_INTENT_MAP = { | |
| "chat": "chat", | |
| "sql": "data_query", | |
| "ambiguous": "chat", | |
| "meta_query": "meta_query", | |
| } | |
| class MLClassification: | |
| """Result from the ML classifier.""" | |
| intent: str # chat | sql | ambiguous | meta_query | |
| route_intent: str # Pipeline-compatible intent for routing | |
| confidence: float # Model confidence (0.0 - 1.0) | |
| method: str = "ml" # Always "ml" for this classifier | |
| class MLIntentClassifier: | |
| """ | |
| ML-based intent classifier using sentence-transformer embeddings | |
| and logistic regression. | |
| Loads a pre-trained model from disk. If the model is unavailable, | |
| classify() returns None so the caller can fall back to heuristics. | |
| """ | |
| def __init__(self, model_path: str = None): | |
| self.model = None | |
| self.encoder = None | |
| self._loaded = False | |
| self._model_path = model_path or _MODEL_PATH | |
| self._try_load() | |
| def _try_load(self): | |
| """Attempt to load the pre-trained model and encoder.""" | |
| if os.environ.get("DISABLE_ML_INTENT", "false").lower() in ("true", "1", "yes"): | |
| logger.info("ml_classifier_disabled_by_env") | |
| return | |
| if not os.path.exists(self._model_path): | |
| logger.info("ml_classifier_model_not_found", path=self._model_path) | |
| return | |
| try: | |
| import joblib | |
| from sentence_transformers import SentenceTransformer | |
| self.model = joblib.load(self._model_path) | |
| self.encoder = SentenceTransformer("all-MiniLM-L6-v2") | |
| self._loaded = True | |
| logger.info("ml_classifier_loaded", path=self._model_path) | |
| except ImportError as e: | |
| logger.warning("ml_classifier_deps_missing", error=str(e), | |
| hint="pip install scikit-learn sentence-transformers joblib") | |
| except Exception as e: | |
| logger.warning("ml_classifier_load_failed", error=str(e)) | |
| def available(self) -> bool: | |
| """Whether the ML model is loaded and ready for inference.""" | |
| return self._loaded and self.model is not None and self.encoder is not None | |
| def classify(self, query: str) -> Optional[MLClassification]: | |
| """ | |
| Classify a query using the ML model. | |
| Returns: | |
| MLClassification if model is available and confident, | |
| None if model is unavailable (caller should fall back to heuristic). | |
| """ | |
| if not self.available: | |
| return None | |
| try: | |
| # Encode the query to a 384-dim embedding | |
| embedding = self.encoder.encode([query]) | |
| # Predict class and confidence | |
| predicted_label = self.model.predict(embedding)[0] | |
| probabilities = self.model.predict_proba(embedding)[0] | |
| confidence = float(max(probabilities)) | |
| route_intent = _ROUTE_INTENT_MAP.get(predicted_label, "data_query") | |
| logger.debug( | |
| "ml_classification", | |
| query=query[:80], | |
| label=predicted_label, | |
| confidence=round(confidence, 3), | |
| route_intent=route_intent, | |
| ) | |
| return MLClassification( | |
| intent=predicted_label, | |
| route_intent=route_intent, | |
| confidence=confidence, | |
| ) | |
| except Exception as e: | |
| logger.warning("ml_classification_failed", error=str(e)) | |
| return None | |
| # ββ Module-level singleton βββββββββββββββββββββββββββββββββββ | |
| # Lazy-loaded on first use to avoid startup cost if not needed. | |
| _classifier_instance: Optional[MLIntentClassifier] = None | |
| def get_ml_classifier() -> MLIntentClassifier: | |
| """Get or create the singleton ML classifier instance.""" | |
| global _classifier_instance | |
| if _classifier_instance is None: | |
| _classifier_instance = MLIntentClassifier() | |
| return _classifier_instance | |