iris-vector-rag 0.2.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.
Files changed (50) hide show
  1. adapters/rag_templates_bridge.py +578 -0
  2. common/__init__.py +1 -0
  3. common/batch_utils.py +294 -0
  4. common/config.py +58 -0
  5. common/connection_manager.py +242 -0
  6. common/connection_pool.py +445 -0
  7. common/database.py +141 -0
  8. common/database_schema_manager.py +289 -0
  9. common/db_init_with_indexes.py +181 -0
  10. common/db_vector_utils.py +181 -0
  11. common/environment_manager.py +220 -0
  12. common/huggingface_utils.py +206 -0
  13. common/iris_client.py +92 -0
  14. common/iris_connection_manager.py +411 -0
  15. common/iris_connection_pool.py +360 -0
  16. common/iris_connector.py +71 -0
  17. common/iris_dbapi_connector.py +323 -0
  18. common/iris_index_utils.py +195 -0
  19. common/iris_sql_utils.py +704 -0
  20. common/iris_stream_reader.py +166 -0
  21. common/llm_cache_config.py +308 -0
  22. common/llm_cache_iris.py +671 -0
  23. common/llm_cache_manager.py +911 -0
  24. common/utils.py +559 -0
  25. common/vector_sql_utils.py +584 -0
  26. evaluation_framework/__init__.py +66 -0
  27. evaluation_framework/biomedical_question_generator.py +826 -0
  28. evaluation_framework/comparative_analysis_system.py +1173 -0
  29. evaluation_framework/empirical_reporting.py +1298 -0
  30. evaluation_framework/evaluation_orchestrator.py +1123 -0
  31. evaluation_framework/execute_production_evaluation.py +382 -0
  32. evaluation_framework/pmc_data_pipeline.py +1332 -0
  33. evaluation_framework/production_evaluation_config.py +353 -0
  34. evaluation_framework/ragas_metrics_framework.py +1013 -0
  35. evaluation_framework/real_production_evaluation.py +544 -0
  36. evaluation_framework/run_10k_evaluation.py +175 -0
  37. evaluation_framework/simple_test.py +364 -0
  38. evaluation_framework/statistical_evaluation_methodology.py +1016 -0
  39. evaluation_framework/test_framework.py +281 -0
  40. evaluation_framework/test_iris_connectivity.py +244 -0
  41. evaluation_framework/test_small_scale_evaluation.py +432 -0
  42. evaluation_framework/true_e2e_evaluation.py +639 -0
  43. evaluation_framework/visualization_engine.py +585 -0
  44. iris_rag/__init__.py +284 -0
  45. iris_rag/config/default_config.yaml +295 -0
  46. iris_vector_rag-0.2.1.dist-info/METADATA +499 -0
  47. iris_vector_rag-0.2.1.dist-info/RECORD +50 -0
  48. iris_vector_rag-0.2.1.dist-info/WHEEL +5 -0
  49. iris_vector_rag-0.2.1.dist-info/entry_points.txt +2 -0
  50. iris_vector_rag-0.2.1.dist-info/top_level.txt +4 -0
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Environment Manager - Smart Environment Detection and Activation
4
+
5
+ This module automatically detects and uses the best available Python environment:
6
+ 1. UV environment (.venv) if available and working
7
+ 2. Current active environment if it has required packages
8
+ 3. System Python as fallback
9
+
10
+ Developers can override by setting FORCE_ENV environment variable or by
11
+ activating their preferred environment before running scripts.
12
+ """
13
+
14
+ import logging
15
+ import os
16
+ import subprocess
17
+ import sys
18
+ from pathlib import Path
19
+ from typing import Any, Dict, Optional
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class EnvironmentManager:
25
+ """Smart environment detection and management."""
26
+
27
+ def __init__(self):
28
+ self.project_root = Path(__file__).parent.parent
29
+ self.uv_env_path = self.project_root / ".venv"
30
+ self.current_env = os.environ.get("VIRTUAL_ENV")
31
+ self.force_env = os.environ.get("FORCE_ENV") # Allow developer override
32
+
33
+ def get_best_python_executable(self) -> str:
34
+ """
35
+ Get the best Python executable to use.
36
+
37
+ Returns:
38
+ Path to Python executable
39
+ """
40
+ # Developer override
41
+ if self.force_env:
42
+ if self.force_env == "system":
43
+ return sys.executable
44
+ elif self.force_env == "uv" and self.uv_env_path.exists():
45
+ return str(self.uv_env_path / "bin" / "python")
46
+ else:
47
+ logger.warning(
48
+ f"FORCE_ENV={self.force_env} not valid, using auto-detection"
49
+ )
50
+
51
+ # If already in a virtual environment and it has required packages, use it
52
+ if self.current_env and self._check_environment_has_iris():
53
+ logger.debug(f"Using current virtual environment: {self.current_env}")
54
+ return sys.executable
55
+
56
+ # Check UV environment if available
57
+ if self.uv_env_path.exists():
58
+ uv_python = self.uv_env_path / "bin" / "python"
59
+ if uv_python.exists() and self._check_environment_has_iris(str(uv_python)):
60
+ logger.debug(f"Using UV environment: {self.uv_env_path}")
61
+ return str(uv_python)
62
+
63
+ # Fallback to current Python
64
+ logger.debug("Using current Python executable")
65
+ return sys.executable
66
+
67
+ def _check_environment_has_iris(self, python_exe: Optional[str] = None) -> bool:
68
+ """
69
+ Check if an environment has the required IRIS packages.
70
+
71
+ Args:
72
+ python_exe: Python executable to check (None for current)
73
+
74
+ Returns:
75
+ True if environment has required packages
76
+ """
77
+ if python_exe is None:
78
+ python_exe = sys.executable
79
+
80
+ try:
81
+ # Quick check for intersystems_irispython package
82
+ result = subprocess.run(
83
+ [
84
+ python_exe,
85
+ "-c",
86
+ "try: import iris; print(hasattr(iris, 'connect')); except ImportError: import iris; print(hasattr(iris, 'connect'))",
87
+ ],
88
+ capture_output=True,
89
+ text=True,
90
+ timeout=5,
91
+ )
92
+
93
+ return result.returncode == 0 and "True" in result.stdout
94
+
95
+ except Exception as e:
96
+ logger.debug(f"Environment check failed for {python_exe}: {e}")
97
+ return False
98
+
99
+ def get_environment_info(self) -> Dict[str, Any]:
100
+ """
101
+ Get information about the current environment setup.
102
+
103
+ Returns:
104
+ Dictionary with environment information
105
+ """
106
+ best_python = self.get_best_python_executable()
107
+
108
+ info = {
109
+ "best_python": best_python,
110
+ "current_python": sys.executable,
111
+ "current_venv": self.current_env,
112
+ "uv_env_exists": self.uv_env_path.exists(),
113
+ "force_env": self.force_env,
114
+ "iris_available": self._check_environment_has_iris(),
115
+ "best_iris_available": self._check_environment_has_iris(best_python),
116
+ }
117
+
118
+ return info
119
+
120
+ def ensure_iris_available(self) -> bool:
121
+ """
122
+ Ensure IRIS packages are available in the best environment.
123
+
124
+ Returns:
125
+ True if IRIS is available, False otherwise
126
+ """
127
+ best_python = self.get_best_python_executable()
128
+ return self._check_environment_has_iris(best_python)
129
+
130
+ def run_in_best_environment(
131
+ self, script_path: str, args: list = None
132
+ ) -> subprocess.CompletedProcess:
133
+ """
134
+ Run a script in the best available environment.
135
+
136
+ Args:
137
+ script_path: Path to Python script
138
+ args: Additional arguments
139
+
140
+ Returns:
141
+ CompletedProcess result
142
+ """
143
+ best_python = self.get_best_python_executable()
144
+ cmd = [best_python, script_path]
145
+ if args:
146
+ cmd.extend(args)
147
+
148
+ logger.debug(f"Running: {' '.join(cmd)}")
149
+ return subprocess.run(cmd, cwd=self.project_root)
150
+
151
+
152
+ def get_iris_connection_in_best_env():
153
+ """
154
+ Get an IRIS connection using the best available environment.
155
+ This function automatically handles environment switching if needed.
156
+ """
157
+ env_manager = EnvironmentManager()
158
+
159
+ # If we're already in the best environment, use direct import
160
+ if env_manager.get_best_python_executable() == sys.executable:
161
+ try:
162
+ import iris
163
+
164
+ if hasattr(iris, "connect"):
165
+ return iris.connect(
166
+ hostname=os.environ.get("IRIS_HOST", "localhost"),
167
+ port=int(os.environ.get("IRIS_PORT", "1974")),
168
+ namespace=os.environ.get("IRIS_NAMESPACE", "USER"),
169
+ username=os.environ.get("IRIS_USERNAME", "SuperUser"),
170
+ password=os.environ.get("IRIS_PASSWORD", "SYS"),
171
+ )
172
+ except Exception as e:
173
+ logger.warning(f"Direct IRIS import failed: {e}")
174
+
175
+ # Otherwise, we need to spawn a subprocess in the correct environment
176
+ logger.info("Switching to optimal environment for IRIS connection")
177
+ raise RuntimeError(
178
+ "IRIS connection requires environment switch. "
179
+ "Please use the connection manager or run in the correct environment."
180
+ )
181
+
182
+
183
+ def check_environment_status():
184
+ """Check and report environment status."""
185
+ env_manager = EnvironmentManager()
186
+ info = env_manager.get_environment_info()
187
+
188
+ print("=== Environment Status ===")
189
+ print(f"Current Python: {info['current_python']}")
190
+ print(f"Best Python: {info['best_python']}")
191
+ print(f"Current VENV: {info['current_venv'] or 'None'}")
192
+ print(f"UV Environment: {'Available' if info['uv_env_exists'] else 'Not found'}")
193
+ print(f"Force Environment: {info['force_env'] or 'Auto-detect'}")
194
+ print(f"IRIS in Current: {'✅' if info['iris_available'] else '❌'}")
195
+ print(f"IRIS in Best: {'✅' if info['best_iris_available'] else '❌'}")
196
+
197
+ if not info["best_iris_available"]:
198
+ print("\n⚠️ Warning: IRIS packages not available in any environment")
199
+ print(" Consider running: uv sync # if using UV")
200
+ print(" Or: pip install intersystems-irispython")
201
+
202
+
203
+ if __name__ == "__main__":
204
+ import argparse
205
+
206
+ parser = argparse.ArgumentParser(description="Environment Manager")
207
+ parser.add_argument("--check", action="store_true", help="Check environment status")
208
+ parser.add_argument("--run", help="Run script in best environment")
209
+ parser.add_argument("args", nargs="*", help="Arguments for script")
210
+
211
+ args = parser.parse_args()
212
+
213
+ if args.check:
214
+ check_environment_status()
215
+ elif args.run:
216
+ env_manager = EnvironmentManager()
217
+ result = env_manager.run_in_best_environment(args.run, args.args)
218
+ sys.exit(result.returncode)
219
+ else:
220
+ parser.print_help()
@@ -0,0 +1,206 @@
1
+ """
2
+ HuggingFace model download utilities with rate limiting and retry logic.
3
+ """
4
+
5
+ import logging
6
+ import random
7
+ import time
8
+ from functools import wraps
9
+ from typing import Any, Optional, Tuple
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def retry_with_exponential_backoff(
15
+ max_retries: int = 5,
16
+ base_delay: float = 1.0,
17
+ max_delay: float = 60.0,
18
+ exponential_base: float = 2.0,
19
+ jitter: bool = True,
20
+ ):
21
+ """
22
+ Decorator for retrying functions with exponential backoff.
23
+
24
+ Args:
25
+ max_retries: Maximum number of retry attempts
26
+ base_delay: Initial delay in seconds
27
+ max_delay: Maximum delay in seconds
28
+ exponential_base: Base for exponential backoff
29
+ jitter: Whether to add random jitter to delays
30
+ """
31
+
32
+ def decorator(func):
33
+ @wraps(func)
34
+ def wrapper(*args, **kwargs):
35
+ last_exception = None
36
+
37
+ for attempt in range(max_retries + 1):
38
+ try:
39
+ return func(*args, **kwargs)
40
+ except Exception as e:
41
+ last_exception = e
42
+
43
+ # Check if this is a rate limiting error
44
+ error_str = str(e).lower()
45
+ is_rate_limit = any(
46
+ indicator in error_str
47
+ for indicator in [
48
+ "rate limit",
49
+ "too many requests",
50
+ "429",
51
+ "quota exceeded",
52
+ "service unavailable",
53
+ "503",
54
+ "timeout",
55
+ ]
56
+ )
57
+
58
+ if attempt == max_retries:
59
+ logger.error(f"Failed after {max_retries} retries: {e}")
60
+ raise last_exception
61
+
62
+ if is_rate_limit:
63
+ # Calculate delay with exponential backoff
64
+ delay = min(base_delay * (exponential_base**attempt), max_delay)
65
+
66
+ # Add jitter to prevent thundering herd
67
+ if jitter:
68
+ delay *= 0.5 + random.random() * 0.5
69
+
70
+ logger.warning(
71
+ f"Rate limit detected (attempt {attempt + 1}/{max_retries + 1}). "
72
+ f"Retrying in {delay:.2f} seconds: {e}"
73
+ )
74
+ time.sleep(delay)
75
+ else:
76
+ # For non-rate-limit errors, fail immediately
77
+ logger.error(f"Non-rate-limit error encountered: {e}")
78
+ raise e
79
+
80
+ raise last_exception
81
+
82
+ return wrapper
83
+
84
+ return decorator
85
+
86
+
87
+ @retry_with_exponential_backoff(max_retries=5, base_delay=2.0, max_delay=120.0)
88
+ def download_huggingface_model(
89
+ model_name: str, trust_remote_code: bool = False, **kwargs
90
+ ) -> Tuple[Any, Any]:
91
+ """
92
+ Download HuggingFace model and tokenizer with retry logic.
93
+
94
+ Args:
95
+ model_name: Name of the model to download
96
+ trust_remote_code: Whether to trust remote code
97
+ **kwargs: Additional arguments for model loading
98
+
99
+ Returns:
100
+ Tuple of (tokenizer, model)
101
+ """
102
+ try:
103
+ from transformers import AutoModel, AutoTokenizer
104
+
105
+ logger.info(f"Downloading HuggingFace model: {model_name}")
106
+
107
+ # Download tokenizer first
108
+ logger.debug(f"Loading tokenizer for {model_name}")
109
+ tokenizer = AutoTokenizer.from_pretrained(
110
+ model_name, trust_remote_code=trust_remote_code, **kwargs
111
+ )
112
+
113
+ # Download model
114
+ logger.debug(f"Loading model for {model_name}")
115
+ model = AutoModel.from_pretrained(
116
+ model_name, trust_remote_code=trust_remote_code, **kwargs
117
+ )
118
+
119
+ logger.info(f"Successfully downloaded HuggingFace model: {model_name}")
120
+ return tokenizer, model
121
+
122
+ except ImportError as e:
123
+ logger.error(f"transformers library not available: {e}")
124
+ raise
125
+ except Exception as e:
126
+ logger.error(f"Failed to download model {model_name}: {e}")
127
+ raise
128
+
129
+
130
+ def get_cached_model(
131
+ model_name: str, cache_dict: dict, trust_remote_code: bool = False, **kwargs
132
+ ) -> Tuple[Any, Any]:
133
+ """
134
+ Get model from cache or download if not cached.
135
+
136
+ Args:
137
+ model_name: Name of the model
138
+ cache_dict: Dictionary to use for caching
139
+ trust_remote_code: Whether to trust remote code
140
+ **kwargs: Additional arguments for model loading
141
+
142
+ Returns:
143
+ Tuple of (tokenizer, model)
144
+ """
145
+ if model_name not in cache_dict:
146
+ logger.info(f"Model {model_name} not in cache, downloading...")
147
+ tokenizer, model = download_huggingface_model(
148
+ model_name, trust_remote_code=trust_remote_code, **kwargs
149
+ )
150
+ cache_dict[model_name] = (tokenizer, model)
151
+ logger.info(f"Cached model {model_name}")
152
+ else:
153
+ logger.info(f"Using cached model {model_name}")
154
+ tokenizer, model = cache_dict[model_name]
155
+
156
+ return tokenizer, model
157
+
158
+
159
+ def clear_model_cache(cache_dict: dict, model_name: Optional[str] = None):
160
+ """
161
+ Clear model cache.
162
+
163
+ Args:
164
+ cache_dict: Dictionary containing cached models
165
+ model_name: Specific model to clear, or None to clear all
166
+ """
167
+ if model_name:
168
+ if model_name in cache_dict:
169
+ del cache_dict[model_name]
170
+ logger.info(f"Cleared cache for model {model_name}")
171
+ else:
172
+ cache_dict.clear()
173
+ logger.info("Cleared all model cache")
174
+
175
+
176
+ # Global cache for models
177
+ _global_model_cache = {}
178
+
179
+
180
+ def get_global_cached_model(
181
+ model_name: str, trust_remote_code: bool = False, **kwargs
182
+ ) -> Tuple[Any, Any]:
183
+ """
184
+ Get model from global cache or download if not cached.
185
+
186
+ Args:
187
+ model_name: Name of the model
188
+ trust_remote_code: Whether to trust remote code
189
+ **kwargs: Additional arguments for model loading
190
+
191
+ Returns:
192
+ Tuple of (tokenizer, model)
193
+ """
194
+ return get_cached_model(
195
+ model_name, _global_model_cache, trust_remote_code, **kwargs
196
+ )
197
+
198
+
199
+ def clear_global_cache(model_name: Optional[str] = None):
200
+ """
201
+ Clear global model cache.
202
+
203
+ Args:
204
+ model_name: Specific model to clear, or None to clear all
205
+ """
206
+ clear_model_cache(_global_model_cache, model_name)
common/iris_client.py ADDED
@@ -0,0 +1,92 @@
1
+ """
2
+ Simple IRIS client for database operations
3
+ """
4
+
5
+ import logging
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from .iris_connection_manager import IRISConnectionManager
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class IRISClient:
14
+ """Simple IRIS database client"""
15
+
16
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
17
+ """Initialize IRIS client with configuration"""
18
+ self.config = config or {}
19
+ self.connection_manager = IRISConnectionManager()
20
+ self._connection = None
21
+
22
+ def connect(self) -> bool:
23
+ """Establish database connection"""
24
+ try:
25
+ self._connection = self.connection_manager.get_connection(self.config)
26
+ return True
27
+ except Exception as e:
28
+ logger.error(f"Failed to connect to IRIS: {e}")
29
+ return False
30
+
31
+ def query(self, sql: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
32
+ """Execute query and return results"""
33
+ if not self._connection:
34
+ if not self.connect():
35
+ raise ConnectionError("No database connection available")
36
+
37
+ try:
38
+ cursor = self._connection.cursor()
39
+ cursor.execute(sql, params or ())
40
+
41
+ # Get column names
42
+ columns = (
43
+ [desc[0] for desc in cursor.description] if cursor.description else []
44
+ )
45
+
46
+ # Fetch results
47
+ rows = cursor.fetchall()
48
+
49
+ # Convert to list of dictionaries
50
+ results = []
51
+ for row in rows:
52
+ results.append(dict(zip(columns, row)))
53
+
54
+ return results
55
+
56
+ except Exception as e:
57
+ logger.error(f"Query failed: {e}")
58
+ raise
59
+
60
+ def execute(self, sql: str, params: Optional[tuple] = None) -> int:
61
+ """Execute non-query SQL statement"""
62
+ if not self._connection:
63
+ if not self.connect():
64
+ raise ConnectionError("No database connection available")
65
+
66
+ try:
67
+ cursor = self._connection.cursor()
68
+ cursor.execute(sql, params or ())
69
+ self._connection.commit()
70
+ return cursor.rowcount
71
+ except Exception as e:
72
+ logger.error(f"Execute failed: {e}")
73
+ self._connection.rollback()
74
+ raise
75
+
76
+ def close(self):
77
+ """Close database connection"""
78
+ if self._connection:
79
+ try:
80
+ self._connection.close()
81
+ except:
82
+ pass
83
+ self._connection = None
84
+
85
+ def __enter__(self):
86
+ """Context manager entry"""
87
+ self.connect()
88
+ return self
89
+
90
+ def __exit__(self, exc_type, exc_val, exc_tb):
91
+ """Context manager exit"""
92
+ self.close()