GitHub Actions commited on
Commit ·
8396c67
1
Parent(s): 6b31aee
Deploy backend from GitHub 28569ae75e2626967d5da9227e391b2d7576d82b
Browse files- backend/app/core/config.py +13 -17
- backend/app/db/firestore.py +23 -9
backend/app/core/config.py
CHANGED
|
@@ -1,17 +1,16 @@
|
|
| 1 |
"""
|
| 2 |
Configuration module for the LLM Misuse Detection backend.
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
REDIS_URL, HF_API_KEY, GROQ_API_KEY,
|
| 7 |
-
SENTRY_DSN, CORS_ORIGINS
|
| 8 |
"""
|
| 9 |
from pydantic_settings import BaseSettings
|
| 10 |
from typing import Optional, List
|
| 11 |
|
|
|
|
|
|
|
| 12 |
|
| 13 |
class Settings(BaseSettings):
|
| 14 |
-
# Application
|
| 15 |
APP_NAME: str = "LLM Misuse Detector"
|
| 16 |
DEBUG: bool = False
|
| 17 |
|
|
@@ -22,23 +21,23 @@ class Settings(BaseSettings):
|
|
| 22 |
# Redis
|
| 23 |
REDIS_URL: str = "redis://localhost:6379/0"
|
| 24 |
|
| 25 |
-
# CORS
|
| 26 |
CORS_ORIGINS: str = "https://security-three-mu.vercel.app,http://localhost:3000"
|
| 27 |
|
| 28 |
-
# HuggingFace
|
| 29 |
HF_API_KEY: str = ""
|
| 30 |
-
HF_DETECTOR_PRIMARY: str = "
|
| 31 |
-
HF_DETECTOR_FALLBACK: str = "
|
| 32 |
-
HF_EMBEDDINGS_PRIMARY: str = "
|
| 33 |
-
HF_EMBEDDINGS_FALLBACK: str = "
|
| 34 |
-
HF_HARM_CLASSIFIER: str = "
|
| 35 |
|
| 36 |
# Groq
|
| 37 |
GROQ_API_KEY: str = ""
|
| 38 |
GROQ_MODEL: str = "llama-3.3-70b-versatile"
|
| 39 |
GROQ_BASE_URL: str = "https://api.groq.com/openai/v1"
|
| 40 |
|
| 41 |
-
#
|
| 42 |
QDRANT_URL: str = "http://localhost:6333"
|
| 43 |
QDRANT_API_KEY: Optional[str] = None
|
| 44 |
QDRANT_COLLECTION: str = "sentinel_embeddings"
|
|
@@ -55,10 +54,7 @@ class Settings(BaseSettings):
|
|
| 55 |
WEIGHT_STYLOMETRY: float = 0.10
|
| 56 |
WEIGHT_WATERMARK: float = 0.05
|
| 57 |
|
| 58 |
-
# Cost control
|
| 59 |
PERPLEXITY_THRESHOLD: float = 0.3
|
| 60 |
-
|
| 61 |
-
# Rate limiting
|
| 62 |
RATE_LIMIT_PER_MINUTE: int = 30
|
| 63 |
|
| 64 |
@property
|
|
|
|
| 1 |
"""
|
| 2 |
Configuration module for the LLM Misuse Detection backend.
|
| 3 |
+
NOTE on HF Inference API (updated July 2025):
|
| 4 |
+
The old api-inference.huggingface.co returns 410 for most models.
|
| 5 |
+
Use router.huggingface.co/hf-inference instead.
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
from pydantic_settings import BaseSettings
|
| 8 |
from typing import Optional, List
|
| 9 |
|
| 10 |
+
_HF_ROUTER = "https://router.huggingface.co/hf-inference/models"
|
| 11 |
+
|
| 12 |
|
| 13 |
class Settings(BaseSettings):
|
|
|
|
| 14 |
APP_NAME: str = "LLM Misuse Detector"
|
| 15 |
DEBUG: bool = False
|
| 16 |
|
|
|
|
| 21 |
# Redis
|
| 22 |
REDIS_URL: str = "redis://localhost:6379/0"
|
| 23 |
|
| 24 |
+
# CORS
|
| 25 |
CORS_ORIGINS: str = "https://security-three-mu.vercel.app,http://localhost:3000"
|
| 26 |
|
| 27 |
+
# HuggingFace
|
| 28 |
HF_API_KEY: str = ""
|
| 29 |
+
HF_DETECTOR_PRIMARY: str = f"{_HF_ROUTER}/roberta-base-openai-detector"
|
| 30 |
+
HF_DETECTOR_FALLBACK: str = f"{_HF_ROUTER}/Hello-SimpleAI/chatgpt-detector-roberta"
|
| 31 |
+
HF_EMBEDDINGS_PRIMARY: str = f"{_HF_ROUTER}/sentence-transformers/all-MiniLM-L6-v2"
|
| 32 |
+
HF_EMBEDDINGS_FALLBACK: str = f"{_HF_ROUTER}/sentence-transformers/paraphrase-MiniLM-L3-v2"
|
| 33 |
+
HF_HARM_CLASSIFIER: str = f"{_HF_ROUTER}/facebook/roberta-hate-speech-dynabench-r4-target"
|
| 34 |
|
| 35 |
# Groq
|
| 36 |
GROQ_API_KEY: str = ""
|
| 37 |
GROQ_MODEL: str = "llama-3.3-70b-versatile"
|
| 38 |
GROQ_BASE_URL: str = "https://api.groq.com/openai/v1"
|
| 39 |
|
| 40 |
+
# Qdrant
|
| 41 |
QDRANT_URL: str = "http://localhost:6333"
|
| 42 |
QDRANT_API_KEY: Optional[str] = None
|
| 43 |
QDRANT_COLLECTION: str = "sentinel_embeddings"
|
|
|
|
| 54 |
WEIGHT_STYLOMETRY: float = 0.10
|
| 55 |
WEIGHT_WATERMARK: float = 0.05
|
| 56 |
|
|
|
|
| 57 |
PERPLEXITY_THRESHOLD: float = 0.3
|
|
|
|
|
|
|
| 58 |
RATE_LIMIT_PER_MINUTE: int = 30
|
| 59 |
|
| 60 |
@property
|
backend/app/db/firestore.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
"""
|
| 2 |
Firebase Admin SDK initialisation and Firestore client.
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
2. GOOGLE_APPLICATION_CREDENTIALS env var – path to the JSON file on disk
|
| 8 |
-
(recommended for local development)
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
import json
|
| 13 |
import os
|
|
@@ -16,11 +16,26 @@ import firebase_admin
|
|
| 16 |
from firebase_admin import credentials, firestore
|
| 17 |
|
| 18 |
from backend.app.core.config import settings
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
_app: firebase_admin.App | None = None
|
| 21 |
_db = None
|
| 22 |
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
def init_firebase() -> None:
|
| 25 |
"""Initialise the Firebase Admin SDK (idempotent)."""
|
| 26 |
global _app, _db
|
|
@@ -28,11 +43,10 @@ def init_firebase() -> None:
|
|
| 28 |
return
|
| 29 |
|
| 30 |
if settings.FIREBASE_CREDENTIALS_JSON:
|
| 31 |
-
# Credentials supplied as a JSON string (production / Render)
|
| 32 |
cred_dict = json.loads(settings.FIREBASE_CREDENTIALS_JSON)
|
|
|
|
| 33 |
cred = credentials.Certificate(cred_dict)
|
| 34 |
elif os.getenv("GOOGLE_APPLICATION_CREDENTIALS"):
|
| 35 |
-
# Path to JSON file on disk (local dev)
|
| 36 |
cred = credentials.ApplicationDefault()
|
| 37 |
else:
|
| 38 |
raise RuntimeError(
|
|
@@ -45,10 +59,10 @@ def init_firebase() -> None:
|
|
| 45 |
{"projectId": settings.FIREBASE_PROJECT_ID},
|
| 46 |
)
|
| 47 |
_db = firestore.client()
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
def get_db():
|
| 51 |
-
"""Return the Firestore client. Call init_firebase() first."""
|
| 52 |
if _db is None:
|
| 53 |
raise RuntimeError("Firestore not initialised. Call init_firebase() on startup.")
|
| 54 |
return _db
|
|
|
|
| 1 |
"""
|
| 2 |
Firebase Admin SDK initialisation and Firestore client.
|
| 3 |
|
| 4 |
+
Fixes:
|
| 5 |
+
- Handles escaped newlines in private_key when FIREBASE_CREDENTIALS_JSON
|
| 6 |
+
is pasted as a single-line string (\\n must become \n for JWT signing).
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
Priority for credentials:
|
| 9 |
+
1. FIREBASE_CREDENTIALS_JSON env var (JSON string, production)
|
| 10 |
+
2. GOOGLE_APPLICATION_CREDENTIALS env var (path to file, local dev)
|
| 11 |
"""
|
| 12 |
import json
|
| 13 |
import os
|
|
|
|
| 16 |
from firebase_admin import credentials, firestore
|
| 17 |
|
| 18 |
from backend.app.core.config import settings
|
| 19 |
+
from backend.app.core.logging import get_logger
|
| 20 |
+
|
| 21 |
+
logger = get_logger(__name__)
|
| 22 |
|
| 23 |
_app: firebase_admin.App | None = None
|
| 24 |
_db = None
|
| 25 |
|
| 26 |
|
| 27 |
+
def _fix_private_key(cred_dict: dict) -> dict:
|
| 28 |
+
"""
|
| 29 |
+
When a service account JSON is pasted as a single-line env var, the
|
| 30 |
+
private_key newlines get double-escaped as \\n instead of \n.
|
| 31 |
+
This causes 'Invalid JWT Signature' errors at runtime.
|
| 32 |
+
Fix: replace literal \\n with real newline in private_key only.
|
| 33 |
+
"""
|
| 34 |
+
if "private_key" in cred_dict:
|
| 35 |
+
cred_dict["private_key"] = cred_dict["private_key"].replace("\\n", "\n")
|
| 36 |
+
return cred_dict
|
| 37 |
+
|
| 38 |
+
|
| 39 |
def init_firebase() -> None:
|
| 40 |
"""Initialise the Firebase Admin SDK (idempotent)."""
|
| 41 |
global _app, _db
|
|
|
|
| 43 |
return
|
| 44 |
|
| 45 |
if settings.FIREBASE_CREDENTIALS_JSON:
|
|
|
|
| 46 |
cred_dict = json.loads(settings.FIREBASE_CREDENTIALS_JSON)
|
| 47 |
+
cred_dict = _fix_private_key(cred_dict)
|
| 48 |
cred = credentials.Certificate(cred_dict)
|
| 49 |
elif os.getenv("GOOGLE_APPLICATION_CREDENTIALS"):
|
|
|
|
| 50 |
cred = credentials.ApplicationDefault()
|
| 51 |
else:
|
| 52 |
raise RuntimeError(
|
|
|
|
| 59 |
{"projectId": settings.FIREBASE_PROJECT_ID},
|
| 60 |
)
|
| 61 |
_db = firestore.client()
|
| 62 |
+
logger.info("Firebase Admin SDK initialised", project=settings.FIREBASE_PROJECT_ID)
|
| 63 |
|
| 64 |
|
| 65 |
def get_db():
|
|
|
|
| 66 |
if _db is None:
|
| 67 |
raise RuntimeError("Firestore not initialised. Call init_firebase() on startup.")
|
| 68 |
return _db
|