Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/ahmadyasin1_insightx-ai.json
T

1 line
13 KiB
JSON

{"repo": "ahmadyasin1/insightx-ai", "files": {"backend/services/huggingface_service.py": {"f7f1283107b9d79a9c5f0b2d323053b3426e9323": {"t": 1785596441.0858092, "n": 11760, "h": "abb8b87e58127e80", "c": "\"\"\"\nHuggingFace Inference API client.\n\nProvides free open-source AI capabilities as:\n- Primary fallback when Anthropic API key is not configured\n- Additional modalities: image captioning, zero-shot classification, summarization\n\nAll models use the free-tier HuggingFace Inference API — no GPU required locally.\n\"\"\"\nimport logging\nfrom typing import List, Optional\n\nimport httpx\n\nlogger = logging.getLogger(__name__)\n\nHF_API_BASE = \"https://api-inference.huggingface.co/models\"\n_DEFAULT_TIMEOUT = 90.0 # HF cold-start can be slow on free tier\n\n\nclass HuggingFaceService:\n \"\"\"Async client for the HuggingFace Hosted Inference API.\"\"\"\n\n _vision_disabled: bool = False\n\n def __init__(self, api_key: str = \"\"):\n self._api_key = api_key\n self._headers = {\"Authorization\": f\"Bearer {api_key}\"} if api_key else {}\n\n @property\n def available(self) -> bool:\n return bool(self._api_key)\n\n async def _post(\n self,\n model: str,\n payload: dict,\n timeout: float = _DEFAULT_TIMEOUT,\n ) -> dict | list:\n url = f\"{HF_API_BASE}/{model}\"\n async with httpx.AsyncClient(timeout=timeout) as client:\n resp = await client.post(url, json=payload, headers=self._headers)\n resp.raise_for_status()\n return resp.json()\n\n async def _post_binary(\n self,\n model: str,\n data: bytes,\n content_type: str = \"image/jpeg\",\n timeout: float = 30.0,\n ) -> dict | list:\n url = f\"{HF_API_BASE}/{model}\"\n headers = {**self._headers, \"Content-Type\": content_type}\n async with httpx.AsyncClient(timeout=timeout) as client:\n resp = await client.post(url, content=data, headers=headers)\n resp.raise_for_status()\n return resp.json()\n\n # ── Text Generation / Chat ────────────────────────────────────────────────\n\n async def chat(\n self,\n prompt: str,\n model: Optional[str] = None,\n max_new_tokens: int = 1024,\n temperature: float = 0.7,\n ) -> str:\n \"\"\"Generate text from a prompt using an instruction-tuned model.\"\"\"\n from config import settings\n target_model = model or settings.HF_CHAT_MODEL\n result = await self._post(target_model, {\n \"inputs\": prompt,\n \"parameters\": {\n \"max_new_tokens\": max_new_tokens,\n \"temperature\": temperature,\n \"do_sample\": True,\n \"return_full_text\": False,\n },\n })\n if isinstance(result, list) and result:\n return result[0].get(\"generated_text\", \"\")\n return str(result)\n\n async def chat_investigation(\n self,\n system_prompt: str,\n context: str,\n question: str,\n model: Optional[str] = None,\n ) -> str:\n \"\"\"Mixtral / LLaMA 3 Instruct-formatted prompt for forensic queries.\"\"\"\n formatted = (\n f\"<s>[INST] <<SYS>>\\n{system_prompt}\\n<</SYS>>\\n\\n\"\n f\"Context:\\n{context[:2000]}\\n\\nQuestion: {question} [/INST]\"\n )\n return await self.chat(formatted, model=model, max_new_tokens=2048)\n\n # ── Summarization ────────────────────────────────────────────────────────\n\n async def summarize(\n self,\n text: str,\n model: Optional[str] = None,\n max_length: int = 256,\n min_length: int = 64,\n ) -> str:\n \"\"\"Abstractive summarization via BART-large-CNN.\"\"\"\n from config import settings\n target_model = model or settings.HF_SUMMARIZATION_MODEL\n result = await self._post(target_model, {\n \"inputs\": text[:3000],\n \"parameters\": {\"max_length\": max_length, \"min_length\": min_length},\n })\n if isinstance(result, list) and result:\n return result[0].get(\"summary_text\", \"\")\n return str(result)\n\n # ── Zero-Shot Text Classification ─────────────────────────────────────────\n\n async def classify_zero_shot(\n self,\n text: str,\n candidate_labels: List[str],\n model: Optional[str] = None,\n ) -> dict:\n \"\"\"Classify text into candidate labels without task-specific training.\"\"\"\n from config import settings\n target_model = model or settings.HF_CLASSIFY_MODEL\n return await self._post(target_model, {\n \"inputs\": text,\n \"parameters\": {\"candidate_labels\": candidate_labels},\n })\n\n # ── Image Captioning ─────────────────────────────────────────────────────\n\n async def caption_image(\n self,\n image_bytes: bytes,\n model: Optional[str] = None,\n ) -> str:\n \"\"\"Generate a natural-language caption for a raw image.\"\"\"\n from config import settings\n target_model = model or settings.HF_CAPTION_MODEL\n result = await self._post_binary(target_model, image_bytes)\n if isinstance(result, list) and result:\n return result[0].get(\"generated_text\", \"\")\n return str(result)\n\n def caption_image_sync(\n self,\n image_bytes: bytes,\n model: Optional[str] = None,\n timeout: float = 60.0,\n ) -> str:\n \"\"\"Synchronous BLIP captioning for use inside Detectra's sync pipeline.\"\"\"\n from config import settings\n target_model = model or settings.HF_CAPTION_MODEL\n url = f\"{HF_API_BASE}/{target_model}\"\n with httpx.Client(timeout=timeout) as client:\n resp = client.post(\n url,\n content=image_bytes,\n headers={**self._headers, \"Content-Type\": \"image/jpeg\"},\n )\n resp.raise_for_status()\n result = resp.json()\n if isinstance(result, list) and result:\n return str(result[0].get(\"generated_text\", \"\")).strip()\n if isinstance(result, dict):\n return str(result.get(\"generated_text\", result.get(\"caption\", \"\"))).strip()\n return str(result).strip()\n\n async def analyze_frame_vlm(\n self,\n image_bytes: bytes,\n prompt: str = \"Describe this surveillance video frame in forensic detail.\",\n model: Optional[str] = None,\n ) -> str:\n \"\"\"Vision-language analysis via HuggingFace router (free-tier VLM).\"\"\"\n from config import settings\n import base64\n\n target_model = model or getattr(settings, \"HF_VLM_MODEL\", \"Qwen/Qwen2-VL-2B-Instruct\")\n b64 = base64.b64encode(image_bytes).decode(\"ascii\")\n payload = {\n \"model\": target_model,\n \"messages\": [{\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": prompt},\n {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{b64}\"}},\n ],\n }],\n \"max_tokens\": 256,\n \"temperature\": 0.2,\n }\n async with httpx.AsyncClient(timeout=120.0) as client:\n resp = await client.post(\n \"https://router.huggingface.co/v1/chat/completions\",\n json=payload,\n headers={**self._headers, \"Content-Type\": \"application/json\"},\n )\n resp.raise_for_status()\n data = resp.json()\n choices = data.get(\"choices\") or []\n if choices:\n return str(choices[0].get(\"message\", {}).get(\"content\", \"\")).strip()\n return \"\"\n\n def analyze_frame_vlm_sync(\n self,\n image_bytes: bytes,\n prompt: str = \"Describe this surveillance video frame in forensic detail.\",\n model: Optional[str] = None,\n ) -> str:\n \"\"\"Sync frame analysis — BLIP caption first, optional VLM router enrichment.\"\"\"\n from config import settings\n\n if not self.available or self._vision_disabled:\n return \"\"\n\n # BLIP via inference API is more reliable on free tier than the vision router.\n try:\n caption = self.caption_image_sync(image_bytes)\n if caption:\n return caption\n except Exception as e:\n logger.debug(f\"BLIP caption failed: {e}\")\n\n target_model = model or getattr(settings, \"HF_VLM_MODEL\", \"Qwen/Qwen2-VL-2B-Instruct\")\n import base64\n\n b64 = base64.b64encode(image_bytes).decode(\"ascii\")\n payload = {\n \"model\": target_model,\n \"messages\": [{\n \"role\": \"user\",\n \"content\": [\n {\"type\": \"text\", \"text\": prompt},\n {\"type\": \"image_url\", \"image_url\": {\"url\": f\"data:image/jpeg;base64,{b64}\"}},\n ],\n }],\n \"max_tokens\": 256,\n \"temperature\": 0.2,\n }\n try:\n with httpx.Client(timeout=120.0) as client:\n resp = client.post(\n \"https://router.huggingface.co/v1/chat/completions\",\n json=payload,\n headers={**self._headers, \"Content-Type\": \"application/json\"},\n )\n resp.raise_for_status()\n data = resp.json()\n choices = data.get(\"choices\") or []\n if choices:\n text = str(choices[0].get(\"message\", {}).get(\"content\", \"\")).strip()\n if text:\n return text\n except Exception as e:\n HuggingFaceService._vision_disabled = True\n logger.warning(f\"Vision APIs unavailable — skipping VLM enhancement: {e}\")\n return \"\"\n\n # ── Investigation-specific helpers ────────────────────────────────────────\n\n async def classify_anomaly_severity(self, description: str) -> str:\n \"\"\"Return a severity level for an anomaly description.\"\"\"\n labels = [\"critical\", \"high\", \"medium\", \"low\", \"informational\"]\n try:\n result = await self.classify_zero_shot(description, labels)\n if isinstance(result, dict) and result.get(\"labels\"):\n return result[\"labels\"][0]\n except Exception as e:\n logger.warning(f\"classify_anomaly_severity failed: {e}\")\n return \"medium\"\n\n async def generate_incident_tags(self, description: str) -> List[str]:\n \"\"\"Return relevant incident tags (score > 0.1) for a description.\"\"\"\n labels = [\n \"violence\", \"loitering\", \"trespassing\", \"theft\", \"fire\",\n \"crowd_formation\", \"traffic_incident\", \"medical_emergency\",\n \"suspicious_behavior\", \"vandalism\", \"unauthorized_access\",\n ]\n try:\n result = await self.classify_zero_shot(description, labels)\n if isinstance(result, dict) and result.get(\"labels\") and result.get(\"scores\"):\n return [\n lbl for lbl, score\n in zip(result[\"labels\"], result[\"scores\"])\n if score > 0.1\n ]\n except Exception as e:\n logger.warning(f\"generate_incident_tags failed: {e}\")\n return []\n\n async def enrich_executive_brief(self, brief: str) -> str:\n \"\"\"Expand a terse AI-generated brief into a paragraph for the report.\"\"\"\n prompt = (\n \"<s>[INST] You are a forensic analyst. Expand the following brief \"\n \"incident summary into a professional 2-3 sentence paragraph suitable \"\n f\"for a police report:\\n\\n{brief} [/INST]\"\n )\n try:\n return await self.chat(prompt, max_new_tokens=256, temperature=0.4)\n except Exception as e:\n logger.warning(f\"enrich_executive_brief failed: {e}\")\n return brief\n\n\ndef get_hf_service() -> HuggingFaceService:\n from config import settings\n key = settings.HUGGINGFACE_API_KEY or settings.HUGGINGFACE_API_KEY_FALLBACK\n return HuggingFaceService(api_key=key)\n"}}}}