Spaces:
Running
Running
| """Named Entity Recognition (NER) based transcript analyzer using GLiNER.""" | |
| from __future__ import annotations | |
| import asyncio | |
| import logging | |
| from .base import EntityMatch, TranscriptAnalyzer | |
| logger = logging.getLogger(__name__) | |
| class EntityAnalyzer(TranscriptAnalyzer): | |
| """Pure entity matcher using GLiNER. Returns list[EntityMatch]. | |
| No callbacks, no deduplication — that's the manager's job. | |
| """ | |
| def __init__(self, entity_labels: list[str]): | |
| """Initialize entity analyzer. | |
| Args: | |
| entity_labels: Entity labels to detect (e.g. ["food", "person"]) | |
| """ | |
| try: | |
| from gliner import GLiNER | |
| except ImportError: | |
| raise ImportError( | |
| "GLiNER not installed. Install with: pip install 'lyon_chatbox[cascade_gliner]'" | |
| ) | |
| from lyon_chatbox.cascade.config import get_config | |
| self.entity_labels = entity_labels | |
| self.model_name = get_config().gliner_model | |
| logger.info(f"Loading GLiNER model: {self.model_name}") | |
| self.model = GLiNER.from_pretrained(self.model_name) | |
| logger.info(f"EntityAnalyzer initialized: {len(entity_labels)} entity types") | |
| async def analyze(self, text: str, is_final: bool) -> list[EntityMatch]: | |
| """Return list of EntityMatch for entities found in text.""" | |
| import time | |
| start_time = time.time() | |
| loop = asyncio.get_event_loop() | |
| entities = await loop.run_in_executor( | |
| None, lambda: self.model.predict_entities(text, self.entity_labels) | |
| ) | |
| elapsed = time.time() - start_time | |
| logger.info(f"GLiNER analyzed '{text[:50]}...' in {elapsed * 1000:.0f}ms, found {len(entities)} entities") | |
| return [ | |
| EntityMatch( | |
| text=e["text"], | |
| label=e["label"], | |
| confidence=e["score"], | |
| ) | |
| for e in entities | |
| ] | |