File size: 3,279 Bytes
b873e20
5849dd3
c8e1f58
5849dd3
 
 
965825a
 
40a8f2e
965825a
b873e20
c8e1f58
 
 
 
5849dd3
c8e1f58
 
5849dd3
 
c8e1f58
5849dd3
c8e1f58
611fd14
 
5849dd3
 
 
 
 
 
 
4d33aa5
965825a
4d33aa5
965825a
 
 
 
 
5849dd3
c8e1f58
 
 
 
 
 
 
5849dd3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e1f58
 
5849dd3
 
 
 
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
87
88
89
90
from functools import lru_cache
from pathlib import Path
from typing import Literal
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

# Single source of truth for the deployed version. app.py, admin.py and all
# outbound User-Agent strings read this constant - never hardcode versions elsewhere.
LIBBEE_VERSION = "3.8.8"

class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        case_sensitive=False,
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
    )
    openai_api_key: str = ""
    anthropic_api_key: str = ""
    primo_api_key: str = ""
    admin_password: str = ""
    session_secret: str = "change-me"
    environment: Literal["development", "production"] = "production"
    maintenance_mode: bool = False
    max_results: int = 5
    runtime_dir: str = "/tmp/libbee_runtime"
    feedback_store_path: str = ""
    metrics_store_path: str = ""
    config_store_path: str = ""
    rag_index_dir: str = ""
    knowledge_dir: str = ""
    rate_limit_per_minute: int = 40
    # Cloudflare Worker URL for persistent analytics
    # Set in HF Space secrets (never hardcode the URL in this repository).
    cloudflare_worker_url: str = ""
    # Shared-secret bearer token for the Worker analytics endpoints (/log, /analytics*, /reports*).
    # Must match the ANALYTICS_TOKEN secret configured on the Cloudflare Worker.
    cloudflare_worker_token: str = ""
    # Contact for polite-pool identification with open scholarly APIs (OpenAlex, Unpaywall, Crossref).
    contact_email: str = "libse@ku.ac.ae"

    @field_validator("max_results")
    @classmethod
    def reasonable_max_results(cls, v: int) -> int:
        if not 1 <= v <= 20:
            raise ValueError("max_results must be between 1 and 20")
        return v

    @field_validator("rate_limit_per_minute")
    @classmethod
    def reasonable_rate_limit(cls, v: int) -> int:
        if not 5 <= v <= 600:
            raise ValueError("rate_limit_per_minute must be between 5 and 600")
        return v

    @property
    def runtime_path(self) -> Path:
        return Path(self.runtime_dir)

    @property
    def feedback_path(self) -> Path:
        return Path(self.feedback_store_path) if self.feedback_store_path else self.runtime_path / "feedback.jsonl"

    @property
    def metrics_path(self) -> Path:
        return Path(self.metrics_store_path) if self.metrics_store_path else self.runtime_path / "metrics.json"

    @property
    def config_path(self) -> Path:
        return Path(self.config_store_path) if self.config_store_path else self.runtime_path / "runtime_config.json"

    @property
    def rag_cache_dir(self) -> Path:
        return Path(self.rag_index_dir) if self.rag_index_dir else self.runtime_path / "rag_cache"

    @property
    def kb_dir(self) -> Path:
        if self.knowledge_dir:
            return Path(self.knowledge_dir)
        if Path("/app/data/knowledge").exists():
            return Path("/app/data/knowledge")
        return Path("data/knowledge")

@lru_cache
def get_settings() -> Settings:
    settings = Settings()
    settings.runtime_path.mkdir(parents=True, exist_ok=True)
    settings.rag_cache_dir.mkdir(parents=True, exist_ok=True)
    return settings