CoolFace
Apppublic

Akshanshsensei/PDF-Constrained-Conversational-Agent

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
config.py296 linesDownload Raw Back to root
1"""2config.py — Central Configuration Module3=========================================4 5Single source of truth for all constants, model names, thresholds, and6credentials used across the PDF-Constrained Conversational Agent.7 8Design principle: FAIL-FAST validation.9  - All required environment variables are validated at import time.10  - The app will crash immediately with a clear error message if any are missing.11  - This prevents mid-demo failures and ensures the system is always in a12    known-good state before any user interaction begins.13 14Usage:15    from config import GEMINI_API_KEY, CHUNK_SIZE, TOP_K_RETRIEVAL, ...16 17Environment variables (set in .env or HF Spaces Secrets):18    GEMINI_API_KEY  — Google Gemini API key19    REDIS_URL       — Redis connection string (e.g. redis://default:pass@host:port)20"""21 22import os23from dotenv import load_dotenv24 25# ---------------------------------------------------------------------------26# Load .env file into os.environ (no-op if already set via system env, which27# means HF Spaces secrets work without any code change at deployment time).28# ---------------------------------------------------------------------------29load_dotenv(override=True)30 31 32def _require_env(key: str) -> str:33    """34    Retrieve a required environment variable, raising at import time if missing.35 36    This implements the fail-fast pattern: rather than returning None and letting37    the error surface later (possibly mid-demo), we crash immediately with an38    actionable message pointing the user to the exact missing variable.39 40    Args:41        key: The environment variable name to look up.42 43    Returns:44        The value of the environment variable as a string.45 46    Raises:47        EnvironmentError: If the variable is not set or is an empty string.48    """49    value = os.getenv(key, "").strip()50    if not value:51        raise EnvironmentError(52            f"[config.py] Required environment variable '{key}' is missing or empty.\n"53            f"  → Add it to your .env file (local) or HF Spaces Secrets (deployment).\n"54            f"  → See .env.example for the expected format."55        )56    return value57 58 59# ---------------------------------------------------------------------------60# CREDENTIALS (fail-fast — these must be set before the app starts)61# ---------------------------------------------------------------------------62 63GEMINI_API_KEY: str = _require_env("GEMINI_API_KEY")64"""Google Gemini API key. Used for embeddings, generation, and faithfulness verification."""65 66REDIS_URL: str = _require_env("REDIS_URL")67"""68Redis connection URL. Format: redis://default:<password>@<host>:<port>69Used for session history storage and PDF index caching.70"""71 72# ---------------------------------------------------------------------------73# PERSISTENCE CONFIGURATION74# ---------------------------------------------------------------------------75 76INDEX_DIR: str = os.getenv("INDEX_DIR", "./index").strip()77"""78Directory to store the persistent FAISS and BM25 index files.79Defaults to './index' in the current working directory.80"""81 82 83 84# ---------------------------------------------------------------------------85# MODEL NAMES86# ---------------------------------------------------------------------------87 88EMBEDDING_MODEL: str = "models/gemini-embedding-2"89"""90Primary embedding model (Gemini). Free tier, 768-dimensional output.91Chosen over OpenAI ada-002 (paid) and older Gemini models (lower quality).92"""93 94GENERATION_MODEL: str = "gemini-2.5-flash"95"""96Generation model. 1M context window, free tier, fast streaming.97Must use 2.5-flash because 2.0-flash has its free tier disabled for newly created API keys.98"""99 100RERANKER_MODEL: str = "BAAI/bge-reranker-m3"101"""102Multilingual Cross-encoder reranker. Runs locally (no API calls, no cost).103BAAI/bge-reranker-m3 supports 100+ languages including Hindi, Chinese, Arabic,104French, German, Spanish and more. Upgraded from ms-marco-MiniLM-L-6-v2 (English-only)105to ensure consistent reranking performance across non-English PDFs.106"""107 108FALLBACK_EMBEDDING_MODEL: str = "paraphrase-multilingual-MiniLM-L12-v2"109"""110Multilingual local sentence-transformers fallback for when Gemini embedding API is unavailable.111384-dimensional output, supports 50+ languages.112Upgraded from all-MiniLM-L6-v2 (English-only) to ensure accurate multilingual113fallback embeddings. Handles Hindi, French, Chinese, Spanish, Arabic and more.114IMPORTANT: If fallback is used, it must be used consistently for ALL vectors115in that session. Mixing models in one FAISS index produces garbage similarities.116See core/embedder.py for the model-consistency enforcement logic.117"""118 119 120# ---------------------------------------------------------------------------121# CHUNKING PARAMETERS122# ---------------------------------------------------------------------------123 124CHUNK_SIZE: int = 500125"""126Chunk size in whitespace-tokenised words (1 token ≈ 1 word for chunking purposes).127 128Rationale: 500 words ≈ 650 sub-word tokens on average, comfortably within the129512-token limit of most bi-encoders, and small enough that a chunk maps clearly130to a specific topic/section.131 132Alternative considered: 256 words (finer granularity, more chunks, higher recall133but more noise). 1000 words (fewer chunks, lower latency but worse precision —134a single chunk may span multiple unrelated topics, confusing the reranker).135 136v1 plan: Replace with semantic chunking using spaCy sentence boundaries.137See core/chunker.py for implementation details.138"""139 140CHUNK_OVERLAP: int = 50141"""142Overlap between consecutive chunks in words.143 144Preserves concept continuity at chunk boundaries. A sentence split across two145chunks will appear in full in at least one of them.146Reference: Module 7 Page 6 — fixed vs overlapping chunking windows.147"""148 149 150# ---------------------------------------------------------------------------151# RETRIEVAL PARAMETERS152# ---------------------------------------------------------------------------153 154TOP_K_RETRIEVAL: int = 25155"""156Number of candidates retrieved from both FAISS and BM25 before fusion.157 158Retrieve broadly for recall (25), rerank precisely for precision (TOP_K_RERANK=8).159Higher values increase recall but add cross-encoder latency linearly.160Increased from 10 to 25 to handle large multi-megabyte PDFs.161"""162 163TOP_K_RERANK: int = 8164"""165Number of chunks passed to the LLM after cross-encoder reranking.166 1678 chunks × ~500 words each ≈ 4000 words of context, well within Gemini's 1M168context window. Enough to answer complex document questions on large PDFs.169Increased from 3 to 8 to reduce false negatives on large datasets.170"""171 172RRF_CONSTANT: int = 60173"""174Reciprocal Rank Fusion constant (k).175 176RRF score formula: sum(1 / (rank + k)) across retrieval systems.177 178k=60 is the standard value from the original paper:179  Cormack, Clarke & Buettcher (2009). "Reciprocal Rank Fusion outperforms180  Condorcet and individual rank learning methods." SIGIR 2009.181 182Higher k dampens the influence of top-ranked results; lower k amplifies it.183k=60 provides robust performance across query types without per-dataset tuning.184"""185 186SIMILARITY_THRESHOLD_GEMINI: float = 0.35187"""188Minimum FAISS cosine similarity score to proceed with retrieval (Gemini embeddings).189 190If the top FAISS result scores below this, we refuse without calling the LLM.191This is Anti-Hallucination Layer 1 — see architecture overview in app.py.192 193Calibrated against gemini-embedding-2 score distribution:194  < 0.25 = unrelated195  0.25-0.35 = loosely / indirectly related (may be semantically adjacent)196  > 0.35 = genuinely relevant197 198Previously 0.4 — lowered to 0.35 to reduce False Negatives on queries on large PDFs199where the exact answer might have slightly lower cosine similarity due to surrounding noise.200'renewable energy and air pollution' when the doc discusses fossil fuels causing201air pollution and renewable energy minimising it).202 203This is a tunable hyperparameter. Increase to tighten refusals, decrease to allow204more borderline retrievals through.205"""206 207SIMILARITY_THRESHOLD_FALLBACK: float = 0.35208"""209Minimum FAISS cosine similarity for the sentence-transformers fallback model.210 211Lower than GEMINI threshold because all-MiniLM-L6-v2 produces a different score212distribution — its vectors are lower-dimensional (384 vs 768) and tend to produce213lower absolute cosine scores for equivalent semantic similarity.214 215Using the same threshold across both models would result in either:216  - Too many false positives with fallback (threshold too low for Gemini)217  - Too many false negatives with Gemini (threshold too high for fallback)218"""219 220 221# ---------------------------------------------------------------------------222# CONVERSATION MEMORY223# ---------------------------------------------------------------------------224 225MAX_HISTORY_MESSAGES: int = 10226"""227Maximum number of messages (user + assistant turns) kept in the active context window.228 229Each message ≈ 200-300 tokens on average. 10 messages ≈ 2000-3000 tokens,230leaving plenty of headroom in Gemini's 1M context for the system prompt + chunks.231 232Truncation strategy: keep the MOST RECENT messages (sliding window, not summary).233v1 plan: Replace sliding window with summarisation of older turns.234"""235 236 237# ---------------------------------------------------------------------------238# REDIS TTL (Time-To-Live) VALUES239# ---------------------------------------------------------------------------240 241REDIS_SESSION_TTL: int = 3600242"""243Session history TTL in seconds (1 hour).244 245TTL is reset on every message write, so active sessions never expire mid-conversation.246Sessions that go idle for >1 hour are automatically cleaned up by Redis.247"""248 249REDIS_INDEX_TTL: int = 86400250"""251PDF index cache TTL in seconds (24 hours).252 253FAISS index, BM25 index, and chunk metadata are stored under this TTL.254After 24 hours, the next upload of the same PDF triggers re-ingestion.255Balances storage cost (Redis free tier: 30MB) vs re-ingestion latency.256"""257 258 259# ---------------------------------------------------------------------------260# HEADER/FOOTER FILTER PARAMETERS (used in core/parser.py)261# ---------------------------------------------------------------------------262 263HEADER_FOOTER_FREQ_THRESHOLD: float = 0.6264"""265Minimum frequency (fraction of pages) for a line to be classified as a266header/footer and stripped.267 2680.6 = appears on 60%+ of pages → likely a repeating header/footer.269Only applied to short lines (< 8 words) to avoid stripping actual content.270"""271 272HEADER_FOOTER_MIN_PAGES: int = 5273"""274Minimum number of pages required before the header/footer filter activates.275 276PDFs with fewer than 5 pages are unlikely to have meaningful repeating headers,277and applying frequency-based filtering on small page counts produces false positives.278"""279 280 281# ---------------------------------------------------------------------------282# LOGGING283# ---------------------------------------------------------------------------284 285LOG_FILE: str = "agent.log"286"""287Output path for the file-based log handler. Written to the project root.288Evaluators can inspect this with: grep "[RETRIEVER]" agent.log289"""290 291LOG_LEVEL: str = "INFO"292"""293Logging verbosity level. Options: DEBUG, INFO, WARNING, ERROR, CRITICAL.294Set to DEBUG for development, INFO for production/evaluation runs.295"""296