File size: 5,497 Bytes
b95549b
50fcf88
 
 
 
 
 
2a7fd26
 
 
 
 
 
 
 
 
 
50fcf88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a7fd26
50fcf88
 
 
 
 
 
 
b95549b
50fcf88
 
 
 
 
 
 
2a7fd26
50fcf88
 
 
 
 
 
2a7fd26
50fcf88
2a7fd26
50fcf88
 
2a7fd26
50fcf88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2a7fd26
 
 
50fcf88
 
2a7fd26
50fcf88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field, field_validator
from typing import Optional
import os
from .definitions import MAX_FILE_SIZE, MAX_TOTAL_SIZE, ALLOWED_TYPES


def _default_chroma_path() -> str:
    if os.environ.get("SPACE_ID"):  # Hugging Face Spaces
        return os.environ.get("CHROMA_DB_PATH", "/tmp/chroma_db")
    return os.environ.get("CHROMA_DB_PATH", "./chroma_db")


def _is_hf() -> bool:
    return os.environ.get("SPACE_ID") is not None


class Settings(BaseSettings):
    """
    Application parameters loaded from environment variables.
    
    For local development:
        Create a .env file in the project root with your configuration:
        GOOGLE_API_KEY=your_api_key_here
    
    For Hugging Face Spaces:
        Add GOOGLE_API_KEY as a secret in Space Settings > Repository secrets
    """
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        extra="ignore",
        case_sensitive=False,
    )

    # File upload parameters with defaults from definitions
    MAX_FILE_SIZE: int = MAX_FILE_SIZE
    MAX_TOTAL_SIZE: int = MAX_TOTAL_SIZE
    ALLOWED_TYPES: list = ALLOWED_TYPES

    # API keys - REQUIRED, must be set via environment variable or HF Secrets
    GOOGLE_API_KEY: str = Field(
        ...,  # Required field
        description="Google API key for Gemini models",
    )

    # Database parameters
    CHROMA_DB_PATH: str = Field(default_factory=_default_chroma_path)

    # Chunking parameters
    CHUNK_SIZE: int = 2000
    CHUNK_OVERLAP: int = 100

    # Retriever parameters
    VECTOR_SEARCH_K: int = 25
    VECTOR_SEARCH_K_CHROMA: int = 15  # Fixed typo: was VECTOR_Search_K_CHROMA
    VECTOR_FETCH_K: int = 35
    VECTOR_SCORE_THRESHOLD: float = 0.3
    BM25_SEARCH_K: int = 8
    HYBRID_RETRIEVER_WEIGHTS: list = [0.4, 0.6]  # [BM25 weight, Vector weight]
    CHROMA_COLLECTION_NAME: str = "documents"

    # Workflow parameters
    MAX_RESEARCH_ATTEMPTS: int = 5
    ENABLE_QUERY_REWRITING: bool = True
    MAX_QUERY_REWRITES: int = 1
    RELEVANCE_CHECK_K: int = 20

    # Research agent parameters
    RESEARCH_TOP_K: int = 15
    RESEARCH_MAX_CONTEXT_CHARS: int = Field(default_factory=lambda: 800_000 if _is_hf() else 8000000000)
    RESEARCH_MAX_OUTPUT_TOKENS: int = 500
    NUM_RESEARCH_CANDIDATES: int = 2  # Number of research questions to generate

    # Verification parameters
    VERIFICATION_MAX_CONTEXT_CHARS: int = Field(default_factory=lambda: 300_000 if _is_hf() else 800000000)
    VERIFICATION_MAX_OUTPUT_TOKENS: int = 300

    # Logging parameters
    LOG_LEVEL: str = "INFO"

    # Cache parameters
    CACHE_DIR: str = "document_cache"
    CACHE_EXPIRE_DAYS: int = 7

    # LLM parameters
    LLM_MAX_RETRIES: int = 3
    LLM_RETRY_DELAY: float = 1.0
    LLM_MODEL_NAME: str = "gemini-2.5-flash-lite"  # Default model for all agents
    
    # Agent-specific LLM models (override LLM_MODEL_NAME if needed)
    RESEARCH_AGENT_MODEL: str = "gemini-2.5-flash-lite"
    VERIFICATION_AGENT_MODEL: str = "gemini-2.5-flash-lite"
    RELEVANCE_CHECKER_MODEL: str = "gemini-2.5-flash-lite"

    # Chart extraction parameters
    ENABLE_CHART_EXTRACTION: bool = True
    CHART_VISION_MODEL: str = "gemini-2.5-flash-lite"
    CHART_MAX_TOKENS: int = 1500
    CHART_DPI: int = Field(default_factory=lambda: 110 if _is_hf() else 110)  # Lower DPI saves memory
    CHART_BATCH_SIZE: int = Field(default_factory=lambda: 1 if _is_hf() else 1)  # Process pages in batches
    CHART_MAX_IMAGE_SIZE: int = Field(default_factory=lambda: 1200 if _is_hf() else 1200)  # Max dimension for images

    # Local chart detection parameters (cost optimization)
    CHART_USE_LOCAL_DETECTION: bool = Field(default_factory=lambda: True if _is_hf() else True)  # Use OpenCV first (FREE)
    CHART_MIN_CONFIDENCE: float = 0.4  # Only analyze charts with confidence > 40%
    CHART_SKIP_GEMINI_DETECTION: bool = True  # Skip Gemini for detection, only use for analysis
    CHART_GEMINI_FALLBACK_ENABLED: bool = False  # Optional: Use Gemini if local fails

    # Gemini batch processing parameters (speed optimization - 2-3× faster)
    CHART_GEMINI_BATCH_SIZE: int = 1  # Analyze 1 chart per API call (reduced from 2 for reliability)
    CHART_ENABLE_BATCH_ANALYSIS: bool = True  # Enable batch processing for speed

    @field_validator("GOOGLE_API_KEY")
    @classmethod
    def validate_api_key(cls, v: str) -> str:
        """Validate that API key is provided and not a placeholder."""
        if not v or v.strip() == "":
            raise ValueError("GOOGLE_API_KEY is required. Set it in your .env file or HF Secrets.")
        if v.startswith("your_") or v == "YOUR_API_KEY_HERE":
            raise ValueError("Please replace the placeholder GOOGLE_API_KEY with your actual API key.")
        return v


def _get_parameters():
    """Get parameters instance with helpful error messages."""
    is_hf_space = os.environ.get("SPACE_ID") is not None
    
    try:
        return Settings()
    except Exception as e:
        import sys
        print(f"⚠️  Configuration Error: {e}", file=sys.stderr)
        
        if is_hf_space:
            print("💡 Tip: Add GOOGLE_API_KEY in Space Settings > Repository secrets", file=sys.stderr)
        else:
            print("💡 Tip: Create a .env file with GOOGLE_API_KEY=your_api_key", file=sys.stderr)
        
        raise


# Create parameters instance
parameters = _get_parameters()