ragit 0.3__py3-none-any.whl → 0.10.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ragit/config.py ADDED
@@ -0,0 +1,204 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ """
6
+ Ragit configuration management with Pydantic validation.
7
+
8
+ Loads configuration from environment variables and .env files.
9
+ Validates all configuration values at startup.
10
+
11
+ Note: As of v0.8.0, ragit no longer has default LLM or embedding models.
12
+ Users must explicitly configure providers.
13
+ """
14
+
15
+ import os
16
+ from pathlib import Path
17
+
18
+ from dotenv import load_dotenv
19
+ from pydantic import BaseModel, Field, field_validator
20
+
21
+ # Note: We define ConfigValidationError locally to avoid circular imports,
22
+ # but ragit.exceptions.ConfigurationError can be used elsewhere
23
+
24
+ # Load .env file from current working directory or project root
25
+ _env_path = Path.cwd() / ".env"
26
+ if _env_path.exists():
27
+ load_dotenv(_env_path)
28
+ else:
29
+ # Try to find .env in parent directories
30
+ for parent in Path.cwd().parents:
31
+ _env_path = parent / ".env"
32
+ if _env_path.exists():
33
+ load_dotenv(_env_path)
34
+ break
35
+
36
+
37
+ class ConfigValidationError(Exception):
38
+ """Raised when configuration validation fails."""
39
+
40
+ pass
41
+
42
+
43
+ class RagitConfig(BaseModel):
44
+ """Validated ragit configuration.
45
+
46
+ All configuration values are validated at startup. Invalid values
47
+ raise ConfigValidationError with a descriptive message.
48
+
49
+ Attributes
50
+ ----------
51
+ ollama_base_url : str
52
+ Ollama server URL (default: http://localhost:11434)
53
+ ollama_embedding_url : str
54
+ Embedding API URL (defaults to ollama_base_url)
55
+ ollama_api_key : str | None
56
+ API key for authentication
57
+ ollama_timeout : int
58
+ Request timeout in seconds (1-600)
59
+ default_llm_model : str | None
60
+ Default LLM model name
61
+ default_embedding_model : str | None
62
+ Default embedding model name
63
+ log_level : str
64
+ Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
65
+ """
66
+
67
+ ollama_base_url: str = Field(default="http://localhost:11434")
68
+ ollama_embedding_url: str | None = None
69
+ ollama_api_key: str | None = None
70
+ ollama_timeout: int = Field(default=120, gt=0, le=600)
71
+ default_llm_model: str | None = None
72
+ default_embedding_model: str | None = None
73
+ log_level: str = Field(default="INFO")
74
+
75
+ @field_validator("ollama_base_url", "ollama_embedding_url", mode="before")
76
+ @classmethod
77
+ def validate_url(cls, v: str | None) -> str | None:
78
+ """Validate URL format."""
79
+ if v is None:
80
+ return v
81
+ v = str(v).strip().rstrip("/")
82
+ if not v:
83
+ return None
84
+ if not v.startswith(("http://", "https://")):
85
+ raise ValueError(f"URL must start with http:// or https://: {v}")
86
+ return v
87
+
88
+ @field_validator("log_level", mode="before")
89
+ @classmethod
90
+ def validate_log_level(cls, v: str) -> str:
91
+ """Validate log level is a valid Python logging level."""
92
+ valid_levels = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
93
+ v = str(v).upper().strip()
94
+ if v not in valid_levels:
95
+ raise ValueError(f"Invalid log level: {v}. Must be one of {valid_levels}")
96
+ return v
97
+
98
+ @field_validator("ollama_api_key", mode="before")
99
+ @classmethod
100
+ def validate_api_key(cls, v: str | None) -> str | None:
101
+ """Treat empty string as None."""
102
+ if v is not None and not str(v).strip():
103
+ return None
104
+ return v
105
+
106
+ @field_validator("ollama_timeout", mode="before")
107
+ @classmethod
108
+ def validate_timeout(cls, v: int | str) -> int:
109
+ """Parse and validate timeout value."""
110
+ try:
111
+ timeout = int(v)
112
+ except (ValueError, TypeError) as e:
113
+ raise ValueError(f"Invalid timeout value '{v}': must be an integer") from e
114
+ return timeout
115
+
116
+ model_config = {"extra": "forbid"}
117
+
118
+ # Uppercase aliases for backwards compatibility
119
+ @property
120
+ def OLLAMA_BASE_URL(self) -> str:
121
+ return self.ollama_base_url
122
+
123
+ @property
124
+ def OLLAMA_EMBEDDING_URL(self) -> str:
125
+ return self.ollama_embedding_url or self.ollama_base_url
126
+
127
+ @property
128
+ def OLLAMA_API_KEY(self) -> str | None:
129
+ return self.ollama_api_key
130
+
131
+ @property
132
+ def OLLAMA_TIMEOUT(self) -> int:
133
+ return self.ollama_timeout
134
+
135
+ @property
136
+ def DEFAULT_LLM_MODEL(self) -> str | None:
137
+ return self.default_llm_model
138
+
139
+ @property
140
+ def DEFAULT_EMBEDDING_MODEL(self) -> str | None:
141
+ return self.default_embedding_model
142
+
143
+ @property
144
+ def LOG_LEVEL(self) -> str:
145
+ return self.log_level
146
+
147
+
148
+ def _safe_get_env(key: str, default: str | None = None) -> str | None:
149
+ """Get environment variable, returning None for empty strings."""
150
+ value = os.getenv(key, default)
151
+ if value is not None and not value.strip():
152
+ return default
153
+ return value
154
+
155
+
156
+ def _safe_get_int_env(key: str, default: int) -> int | str:
157
+ """Get environment variable as int, returning raw string if invalid."""
158
+ value = os.getenv(key)
159
+ if value is None:
160
+ return default
161
+ try:
162
+ return int(value)
163
+ except ValueError:
164
+ # Return the raw string so Pydantic can give a better error message
165
+ return value
166
+
167
+
168
+ def load_config() -> RagitConfig:
169
+ """Load and validate configuration from environment variables.
170
+
171
+ Returns
172
+ -------
173
+ RagitConfig
174
+ Validated configuration object.
175
+
176
+ Raises
177
+ ------
178
+ ConfigValidationError
179
+ If configuration validation fails.
180
+ """
181
+ try:
182
+ return RagitConfig(
183
+ ollama_base_url=_safe_get_env("OLLAMA_BASE_URL", "http://localhost:11434") or "http://localhost:11434",
184
+ ollama_embedding_url=_safe_get_env("OLLAMA_EMBEDDING_URL") or _safe_get_env("OLLAMA_BASE_URL"),
185
+ ollama_api_key=_safe_get_env("OLLAMA_API_KEY"),
186
+ ollama_timeout=_safe_get_int_env("OLLAMA_TIMEOUT", 120),
187
+ default_llm_model=_safe_get_env("RAGIT_DEFAULT_LLM_MODEL"),
188
+ default_embedding_model=_safe_get_env("RAGIT_DEFAULT_EMBEDDING_MODEL"),
189
+ log_level=_safe_get_env("RAGIT_LOG_LEVEL", "INFO") or "INFO",
190
+ )
191
+ except Exception as e:
192
+ raise ConfigValidationError(f"Configuration error: {e}") from e
193
+
194
+
195
+ # Singleton instance - validates configuration at import time
196
+ try:
197
+ config = load_config()
198
+ except ConfigValidationError as e:
199
+ # Re-raise with clear message
200
+ raise ConfigValidationError(str(e)) from e
201
+
202
+
203
+ # Backwards compatibility alias
204
+ Config = RagitConfig
ragit/core/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ """Ragit core module."""
@@ -0,0 +1,22 @@
1
+ #
2
+ # Copyright RODMENA LIMITED 2025
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ """Ragit experiment module."""
6
+
7
+ from ragit.core.experiment.experiment import (
8
+ BenchmarkQuestion,
9
+ Document,
10
+ RAGConfig,
11
+ RagitExperiment,
12
+ )
13
+ from ragit.core.experiment.results import EvaluationResult, ExperimentResults
14
+
15
+ __all__ = [
16
+ "RagitExperiment",
17
+ "Document",
18
+ "BenchmarkQuestion",
19
+ "RAGConfig",
20
+ "EvaluationResult",
21
+ "ExperimentResults",
22
+ ]