File size: 5,454 Bytes
af8ac78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""Document AI Expert β€” Central Configuration.

Mode flags are driven by environment variables set by start.sh:
  HF_MODE=1    β†’ HuggingFace Spaces low-resource mode (CPU, small models, no Neo4j)
  ADMIN_MODE=0 β†’ Disable admin controls (set by -noadmin CLI switch)
"""
import os
from pathlib import Path
import contextvars

BASE_DIR = Path(__file__).parent
current_session = contextvars.ContextVar("current_session", default="admin")

# ── Operating Mode Flags ───────────────────────────────────────────────────────
# HF_MODE: True when running in HuggingFace Spaces or via `python app.py -hf`
# Also triggers for SPACE_ID env var (native HF Spaces detection)
_hf_space   = bool(os.getenv("SPACE_ID"))
HF_MODE     = bool(os.getenv("HF_MODE")) or _hf_space

# ADMIN_MODE: False disables all admin API routes and UI controls.
# Defaults to True (admin enabled) unless explicitly set to "0" or "false".
_admin_env  = os.getenv("ADMIN_MODE", "1").lower()
ADMIN_MODE  = _admin_env not in ("0", "false", "no")

# ── LLM generation server ──────────────────────────────────────────────────────
LLM_BASE_URL        = os.getenv("LLM_BASE_URL", "http://127.0.0.1:8002")
LLM_COMPLETIONS_URL = f"{LLM_BASE_URL}/v1/completions"

# Model selection:
#   GPU & HF mode  β†’ Jackrong/Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF
_default_model   = "Jackrong/Qwen3.5-4B-Claude-4.6-Opus-Reasoning-Distilled-GGUF"
LLM_MODEL_ID     = os.getenv("LLM_MODEL_ID",      _default_model)
LLM_MODEL_FILENAME = os.getenv("LLM_MODEL_FILENAME", "Qwen3.5-4B.Q4_K_M.gguf")

# Token limits: lower in HF mode to keep CPU inference under ~60 s
_default_max_tokens = "512" if HF_MODE else "2048"
LLM_MAX_TOKENS   = int(os.getenv("LLM_MAX_TOKENS",   _default_max_tokens))
LLM_TEMPERATURE  = float(os.getenv("LLM_TEMPERATURE", "0.1"))
LLM_TOP_P        = float(os.getenv("LLM_TOP_P",       "0.9"))
LLM_TIMEOUT      = int(os.getenv("LLM_TIMEOUT",      "1200"))

# ── Embedding server ───────────────────────────────────────────────────────────
EMBED_BASE_URL       = os.getenv("EMBED_BASE_URL",  "http://127.0.0.1:8003")
EMBED_EMBEDDINGS_URL = f"{EMBED_BASE_URL}/v1/embeddings"

# Embedding model:
#   GPU & HF mode  β†’ bge-small-en-v1.5 (~130 MB, 384 dim)
_default_embed_model = "BAAI/bge-small-en-v1.5"
EMBEDDING_MODEL      = os.getenv("EMBEDDING_MODEL", _default_embed_model)

# Batch size: smaller in HF mode to avoid RAM spikes during ingestion
_default_batch = "2" if HF_MODE else "12"
EMBEDDING_BATCH_SIZE = int(os.getenv("EMBEDDING_BATCH_SIZE", _default_batch))
EMBEDDING_TIMEOUT    = int(os.getenv("EMBEDDING_TIMEOUT",    "120"))

# KV-cache precompilation: disabled in HF mode (holds full KB text in RAM)
KV_CACHE_ENABLED     = not HF_MODE

# ── ChromaDB & Security ────────────────────────────────────────────────────────
CHROMA_PERSIST_DIR  = os.getenv("CHROMA_PERSIST_DIR", str(BASE_DIR / "data" / "chroma_db"))
CHROMA_COLLECTION   = os.getenv("CHROMA_COLLECTION",  "Document")
ENCRYPTION_KEY_FILE = os.getenv("ENCRYPTION_KEY_FILE", str(BASE_DIR / "data" / "security.key"))

# ── Kuzu Graph Database ──────────────────────────────────────────────────────────
KUZU_DB_PATH     = os.getenv("KUZU_DB_PATH", str(BASE_DIR / "data" / "kuzu_db"))
GRAPH_AVAILABLE  = True  # Kuzu is embedded, works everywhere including HF Spaces

# ── Flask / Upload ─────────────────────────────────────────────────────────────
UPLOAD_FOLDER       = os.getenv("UPLOAD_FOLDER", str(BASE_DIR / "uploads"))
MAX_CONTENT_LENGTH  = 5 * 1024 * 1024  # 5 MB limit
ALLOWED_EXTENSIONS  = {".txt", ".pdf", ".docx", ".xlsx", ".csv", ".png", ".jpg", ".jpeg", ".webp"}
SECRET_KEY          = os.getenv("SECRET_KEY", "healthexpert-dev-key-change-in-prod")

# ── RAG ────────────────────────────────────────────────────────────────────────
CHUNK_SIZE    = int(os.getenv("CHUNK_SIZE",    "512"))
CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "64"))
TOP_K_VECTOR  = int(os.getenv("TOP_K_VECTOR",  "10"))
TOP_K_GRAPH   = int(os.getenv("TOP_K_GRAPH",   "10"))

# ── HuggingFace model cache ────────────────────────────────────────────────────
_models_dir = BASE_DIR.parent / "models"
HF_HOME     = str(_models_dir) if _models_dir.exists() else str(BASE_DIR / "models")
os.environ.setdefault("HF_HOME", HF_HOME)

# ── System info (for resource banner) ─────────────────────────────────────────
SYSTEM_INFO_ENABLED = True   # /api/sysinfo endpoint always active