feat/Update_Controllers_to_Allow_for_Premium_Membership

This commit is contained in:
cesnimda
2026-08-03 09:17:28 +02:00
parent de937d25dc
commit c3f4a57195
187 changed files with 26062 additions and 991 deletions
+28 -5
View File
@@ -17,6 +17,7 @@ import torch
import pytesseract
from urllib import request as urllib_request
from urllib.error import URLError, HTTPError
from contextvars import ContextVar
app = FastAPI(title="Local AI Service")
@@ -33,6 +34,8 @@ AI_SERVICE_TOKEN_HEADER = "X-Ai-Service-Token"
# /health stays open: the backend probe and the compose healthcheck both call it, and it
# exposes no user data and no generation path.
AI_SERVICE_OPEN_PATHS = {"/health"}
EXTERNAL_AI_ALLOWED_HEADER = "X-Ai-External-Allowed"
_external_ai_allowed = ContextVar("external_ai_allowed", default=False)
@app.middleware("http")
@@ -45,7 +48,18 @@ async def require_service_token(request: Request, call_next):
{"detail": "Invalid or missing service token."},
status_code=401,
)
return await call_next(request)
allowed = (
EXTERNAL_AI_ENABLED
and request.headers.get(EXTERNAL_AI_ALLOWED_HEADER, "").strip().lower() == "true"
)
token = _external_ai_allowed.set(allowed)
try:
response = await call_next(request)
if request.url.path.startswith("/cv/"):
response.headers["X-Ai-Provider"] = _effective_provider()
return response
finally:
_external_ai_allowed.reset(token)
MODEL_NAME = "sshleifer/distilbart-cnn-12-6"
MAX_INPUT_CHARS = 20000
@@ -61,6 +75,7 @@ OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "")
# Default stays "ollama" so the service works keyless/local. /summarize stays local
# (distilbart) regardless of this setting.
AI_PROVIDER = (os.getenv("AI_PROVIDER", "ollama").strip().lower() or "ollama")
EXTERNAL_AI_ENABLED = os.getenv("EXTERNAL_AI_ENABLED", "").strip().lower() in {"1", "true", "yes"}
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "").strip()
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.0-flash").strip()
GEMINI_BASE_URL = os.getenv("GEMINI_BASE_URL", "https://generativelanguage.googleapis.com").rstrip("/")
@@ -448,6 +463,12 @@ def _provider_configured() -> bool:
return bool(OLLAMA_MODEL)
def _effective_provider() -> str:
if EXTERNAL_AI_ENABLED and _external_ai_allowed.get() and AI_PROVIDER in {"gemini", "groq"}:
return AI_PROVIDER
return "ollama"
def _http_post_json(url: str, payload: dict, headers: dict, timeout: int) -> dict:
data = json.dumps(payload).encode("utf-8")
req = urllib_request.Request(
@@ -514,7 +535,7 @@ def _groq_generate(prompt: str, *, json_mode: bool, temperature: float, timeout:
def _provider_generate(prompt: str, *, json_mode: bool, temperature: float, timeout: int) -> str:
provider = AI_PROVIDER
provider = _effective_provider()
try:
if provider == "gemini":
return _gemini_generate(prompt, json_mode=json_mode, temperature=temperature, timeout=timeout)
@@ -530,9 +551,10 @@ def _provider_generate(prompt: str, *, json_mode: bool, temperature: float, time
def _ollama_generate_json(prompt: str):
provider = _effective_provider()
raw = _provider_generate(prompt, json_mode=True, temperature=0.1, timeout=120)
if not raw:
raise HTTPException(status_code=502, detail=f"{_provider_display(AI_PROVIDER)} returned an empty response.")
raise HTTPException(status_code=502, detail=f"{_provider_display(provider)} returned an empty response.")
try:
return json.loads(raw)
except json.JSONDecodeError:
@@ -540,13 +562,14 @@ def _ollama_generate_json(prompt: str):
end = raw.rfind("}")
if start >= 0 and end > start:
return json.loads(raw[start:end + 1])
raise HTTPException(status_code=502, detail=f"{_provider_display(AI_PROVIDER)} did not return valid JSON.")
raise HTTPException(status_code=502, detail=f"{_provider_display(provider)} did not return valid JSON.")
def _ollama_generate_text(prompt: str) -> str:
provider = _effective_provider()
raw = _provider_generate(prompt, json_mode=False, temperature=0.2, timeout=180)
if not raw:
raise HTTPException(status_code=502, detail=f"{_provider_display(AI_PROVIDER)} returned an empty rewrite.")
raise HTTPException(status_code=502, detail=f"{_provider_display(provider)} returned an empty rewrite.")
return raw
+36 -4
View File
@@ -11,7 +11,7 @@ if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
def load_app_module(monkeypatch, *, skip_model_load=True, ollama_model=None, service_token=None):
def load_app_module(monkeypatch, *, skip_model_load=True, ollama_model=None, service_token=None, external_ai_enabled=False):
if skip_model_load:
monkeypatch.setenv("AI_SERVICE_SKIP_MODEL_LOAD", "1")
else:
@@ -26,6 +26,10 @@ def load_app_module(monkeypatch, *, skip_model_load=True, ollama_model=None, ser
monkeypatch.delenv("OLLAMA_MODEL", raising=False)
else:
monkeypatch.setenv("OLLAMA_MODEL", ollama_model)
if external_ai_enabled:
monkeypatch.setenv("EXTERNAL_AI_ENABLED", "true")
else:
monkeypatch.delenv("EXTERNAL_AI_ENABLED", raising=False)
if "app" in sys.modules:
del sys.modules["app"]
module = importlib.import_module("app")
@@ -227,7 +231,8 @@ def test_provider_gemini_dispatch(monkeypatch):
monkeypatch.setenv("AI_PROVIDER", "gemini")
monkeypatch.setenv("GEMINI_API_KEY", "test-key")
monkeypatch.setenv("GEMINI_MODEL", "gemini-2.0-flash")
module = load_app_module(monkeypatch)
module = load_app_module(monkeypatch, external_ai_enabled=True)
module._external_ai_allowed.set(True)
captured = {}
payload = {"candidates": [{"content": {"parts": [{"text": '{"score": 9}'}]}}]}
@@ -244,7 +249,8 @@ def test_provider_gemini_dispatch(monkeypatch):
def test_provider_groq_dispatch(monkeypatch):
monkeypatch.setenv("AI_PROVIDER", "groq")
monkeypatch.setenv("GROQ_API_KEY", "test-key")
module = load_app_module(monkeypatch)
module = load_app_module(monkeypatch, external_ai_enabled=True)
module._external_ai_allowed.set(True)
captured = {}
payload = {"choices": [{"message": {"content": "rewritten CV text"}}]}
@@ -259,7 +265,8 @@ def test_provider_groq_dispatch(monkeypatch):
def test_provider_missing_cloud_key_raises_503(monkeypatch):
monkeypatch.setenv("AI_PROVIDER", "gemini")
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
module = load_app_module(monkeypatch)
module = load_app_module(monkeypatch, external_ai_enabled=True)
module._external_ai_allowed.set(True)
from fastapi import HTTPException
@@ -284,6 +291,31 @@ def test_health_reports_active_provider(monkeypatch):
assert payload["ai_provider_configured"] is True
def test_external_provider_requires_admin_gate_and_backend_consent_header(monkeypatch):
monkeypatch.setenv("AI_PROVIDER", "gemini")
monkeypatch.setenv("GEMINI_API_KEY", "test-key")
module = load_app_module(monkeypatch, ollama_model="qwen2.5:7b", external_ai_enabled=True)
captured = {}
_install_fake_urlopen(monkeypatch, module, {"response": "rewritten locally"}, captured)
client = TestClient(module.app)
local_response = client.post("/cv/rewrite", json={"instruction": "Rewrite", "text": "Synthetic CV"})
assert local_response.status_code == 200
assert captured["url"].startswith("http://127.0.0.1:11434/")
assert local_response.headers["X-Ai-Provider"] == "ollama"
external_payload = {"candidates": [{"content": {"parts": [{"text": "rewritten externally"}]}}]}
_install_fake_urlopen(monkeypatch, module, external_payload, captured)
external_response = client.post(
"/cv/rewrite",
json={"instruction": "Rewrite", "text": "Synthetic CV"},
headers={"X-Ai-External-Allowed": "true"},
)
assert external_response.status_code == 200
assert "generativelanguage" in captured["url"]
assert external_response.headers["X-Ai-Provider"] == "gemini"
def test_service_token_rejects_calls_without_the_header(monkeypatch):
module = load_app_module(monkeypatch, service_token="s3cret")
client = TestClient(module.app)