GitHub Actions commited on
Commit
8396c67
·
1 Parent(s): 6b31aee

Deploy backend from GitHub 28569ae75e2626967d5da9227e391b2d7576d82b

Browse files
backend/app/core/config.py CHANGED
@@ -1,17 +1,16 @@
1
  """
2
  Configuration module for the LLM Misuse Detection backend.
3
- Reads all settings from environment variables.
4
-
5
- Env vars: FIREBASE_PROJECT_ID, FIREBASE_CREDENTIALS_JSON,
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 – defaults include both production frontend and local dev
26
  CORS_ORIGINS: str = "https://security-three-mu.vercel.app,http://localhost:3000"
27
 
28
- # HuggingFace Inference API
29
  HF_API_KEY: str = ""
30
- HF_DETECTOR_PRIMARY: str = "https://api-inference.huggingface.co/models/desklib/ai-text-detector-v1.01"
31
- HF_DETECTOR_FALLBACK: str = "https://api-inference.huggingface.co/models/fakespot-ai/roberta-base-ai-text-detection-v1"
32
- HF_EMBEDDINGS_PRIMARY: str = "https://api-inference.huggingface.co/models/sentence-transformers/all-mpnet-base-v2"
33
- HF_EMBEDDINGS_FALLBACK: str = "https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2"
34
- HF_HARM_CLASSIFIER: str = "https://api-inference.huggingface.co/models/facebook/roberta-hate-speech-dynabench-r4-target"
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
- # Vector DB (Qdrant Cloud)
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
- Priority for credentials (in order):
5
- 1. FIREBASE_CREDENTIALS_JSON env var – JSON string of the service account
6
- (paste the whole file contents as a single escaped string in Render/CI)
7
- 2. GOOGLE_APPLICATION_CREDENTIALS env var – path to the JSON file on disk
8
- (recommended for local development)
9
 
10
- Call `get_db()` anywhere to obtain the Firestore async client.
 
 
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