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,360 @@
1
+ """
2
+ IRIS Connection Pool for high-performance production workloads.
3
+
4
+ Implements connection pooling to eliminate the connection overhead observed
5
+ in production (60s per 100-ticket batch from connection churn).
6
+
7
+ Features:
8
+ - Thread-safe connection pool with min/max sizing
9
+ - Automatic connection validation and refresh
10
+ - Graceful degradation on pool exhaustion
11
+ - Connection health checks
12
+ - Pool statistics for monitoring
13
+ """
14
+
15
+ import logging
16
+ import os
17
+ import threading
18
+ import time
19
+ from queue import Queue, Empty, Full
20
+ from typing import Optional, Dict, Any
21
+ from contextlib import contextmanager
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class IRISConnectionPool:
27
+ """
28
+ Thread-safe connection pool for InterSystems IRIS.
29
+
30
+ Maintains a pool of reusable database connections to eliminate
31
+ connection overhead in high-throughput scenarios.
32
+
33
+ Example:
34
+ pool = IRISConnectionPool(min_size=5, max_size=20)
35
+
36
+ with pool.get_connection() as conn:
37
+ cursor = conn.cursor()
38
+ cursor.execute("SELECT * FROM RAG.Entities LIMIT 10")
39
+ results = cursor.fetchall()
40
+ cursor.close()
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ min_size: int = 5,
46
+ max_size: int = 20,
47
+ connection_timeout: float = 30.0,
48
+ validation_interval: int = 60,
49
+ host: str = None,
50
+ port: int = None,
51
+ namespace: str = None,
52
+ username: str = None,
53
+ password: str = None
54
+ ):
55
+ """
56
+ Initialize connection pool.
57
+
58
+ Args:
59
+ min_size: Minimum connections to maintain
60
+ max_size: Maximum connections allowed
61
+ connection_timeout: Timeout in seconds when acquiring connection
62
+ validation_interval: Seconds between connection health checks
63
+ host: IRIS host (defaults to IRIS_HOST env var)
64
+ port: IRIS port (defaults to IRIS_PORT env var)
65
+ namespace: IRIS namespace (defaults to IRIS_NAMESPACE env var)
66
+ username: IRIS username (defaults to IRIS_USERNAME env var)
67
+ password: IRIS password (defaults to IRIS_PASSWORD env var)
68
+ """
69
+ self.min_size = min_size
70
+ self.max_size = max_size
71
+ self.connection_timeout = connection_timeout
72
+ self.validation_interval = validation_interval
73
+
74
+ # Connection parameters
75
+ self.host = host or os.environ.get("IRIS_HOST", "localhost")
76
+ self.port = port or int(os.environ.get("IRIS_PORT", 1972))
77
+ self.namespace = namespace or os.environ.get("IRIS_NAMESPACE", "USER")
78
+ self.username = username or os.environ.get("IRIS_USERNAME", "_SYSTEM")
79
+ self.password = password or os.environ.get("IRIS_PASSWORD", "SYS")
80
+
81
+ # Pool state
82
+ self._pool = Queue(maxsize=max_size)
83
+ self._active_connections = 0
84
+ self._lock = threading.Lock()
85
+ self._closed = False
86
+
87
+ # Statistics
88
+ self._stats = {
89
+ "created": 0,
90
+ "destroyed": 0,
91
+ "hits": 0,
92
+ "misses": 0,
93
+ "timeouts": 0,
94
+ "validation_failures": 0
95
+ }
96
+
97
+ # Initialize pool with minimum connections
98
+ self._initialize_pool()
99
+
100
+ logger.info(
101
+ f"IRIS Connection Pool initialized: min={min_size}, max={max_size}, "
102
+ f"host={self.host}:{self.port}/{self.namespace}"
103
+ )
104
+
105
+ def _initialize_pool(self):
106
+ """Create minimum number of connections on startup."""
107
+ for i in range(self.min_size):
108
+ try:
109
+ conn = self._create_connection()
110
+ if conn:
111
+ self._pool.put(conn, block=False)
112
+ self._stats["created"] += 1
113
+ logger.debug(f"Created initial connection {i+1}/{self.min_size}")
114
+ except Full:
115
+ logger.warning(f"Pool full during initialization at {i+1} connections")
116
+ break
117
+ except Exception as e:
118
+ logger.error(f"Failed to create initial connection {i+1}: {e}")
119
+
120
+ def _create_connection(self):
121
+ """
122
+ Create a new IRIS database connection.
123
+
124
+ Returns:
125
+ Connection object or None if creation fails
126
+ """
127
+ try:
128
+ # Import here to avoid circular dependencies
129
+ from .iris_dbapi_connector import get_iris_dbapi_connection
130
+
131
+ conn = get_iris_dbapi_connection()
132
+
133
+ if conn is None:
134
+ logger.error("Failed to create IRIS connection")
135
+ return None
136
+
137
+ with self._lock:
138
+ self._active_connections += 1
139
+
140
+ logger.debug(
141
+ f"Created new connection (active: {self._active_connections}/{self.max_size})"
142
+ )
143
+ return conn
144
+
145
+ except Exception as e:
146
+ logger.error(f"Exception creating IRIS connection: {e}")
147
+ return None
148
+
149
+ def _validate_connection(self, conn) -> bool:
150
+ """
151
+ Check if connection is still valid.
152
+
153
+ Args:
154
+ conn: Connection to validate
155
+
156
+ Returns:
157
+ True if valid, False otherwise
158
+ """
159
+ try:
160
+ cursor = conn.cursor()
161
+ cursor.execute("SELECT 1")
162
+ result = cursor.fetchone()
163
+ cursor.close()
164
+ return result is not None
165
+ except Exception as e:
166
+ logger.debug(f"Connection validation failed: {e}")
167
+ self._stats["validation_failures"] += 1
168
+ return False
169
+
170
+ def _destroy_connection(self, conn):
171
+ """
172
+ Close and destroy a connection.
173
+
174
+ Args:
175
+ conn: Connection to destroy
176
+ """
177
+ try:
178
+ conn.close()
179
+ with self._lock:
180
+ self._active_connections -= 1
181
+ self._stats["destroyed"] += 1
182
+ logger.debug(f"Destroyed connection (active: {self._active_connections})")
183
+ except Exception as e:
184
+ logger.warning(f"Error destroying connection: {e}")
185
+
186
+ @contextmanager
187
+ def get_connection(self, timeout: float = None):
188
+ """
189
+ Get a connection from the pool (context manager).
190
+
191
+ Usage:
192
+ with pool.get_connection() as conn:
193
+ # Use connection
194
+ pass
195
+
196
+ Args:
197
+ timeout: Override default connection timeout
198
+
199
+ Yields:
200
+ Database connection
201
+
202
+ Raises:
203
+ TimeoutError: If connection not available within timeout
204
+ RuntimeError: If pool is closed
205
+ """
206
+ if self._closed:
207
+ raise RuntimeError("Connection pool is closed")
208
+
209
+ timeout = timeout or self.connection_timeout
210
+ conn = None
211
+ acquired_from_pool = False
212
+
213
+ try:
214
+ # Try to get connection from pool
215
+ try:
216
+ conn = self._pool.get(block=True, timeout=timeout)
217
+ acquired_from_pool = True
218
+ self._stats["hits"] += 1
219
+ logger.debug("Acquired connection from pool")
220
+
221
+ # Validate connection
222
+ if not self._validate_connection(conn):
223
+ logger.warning("Pooled connection invalid, creating new one")
224
+ self._destroy_connection(conn)
225
+ conn = self._create_connection()
226
+ acquired_from_pool = False
227
+ self._stats["misses"] += 1
228
+
229
+ except Empty:
230
+ # Pool empty, try to create new connection if under limit
231
+ with self._lock:
232
+ if self._active_connections < self.max_size:
233
+ conn = self._create_connection()
234
+ self._stats["misses"] += 1
235
+ logger.debug("Created new connection (pool empty)")
236
+ else:
237
+ self._stats["timeouts"] += 1
238
+ raise TimeoutError(
239
+ f"Connection pool exhausted (max={self.max_size}) "
240
+ f"and timeout ({timeout}s) reached"
241
+ )
242
+
243
+ if conn is None:
244
+ raise RuntimeError("Failed to acquire database connection")
245
+
246
+ yield conn
247
+
248
+ finally:
249
+ # Return connection to pool if it's still valid
250
+ if conn is not None:
251
+ if acquired_from_pool or self._pool.qsize() < self.max_size:
252
+ try:
253
+ # Re-validate before returning to pool
254
+ if self._validate_connection(conn):
255
+ self._pool.put(conn, block=False)
256
+ logger.debug("Returned connection to pool")
257
+ else:
258
+ logger.warning("Connection invalid, destroying instead of returning to pool")
259
+ self._destroy_connection(conn)
260
+ except Full:
261
+ # Pool full, destroy the connection
262
+ logger.debug("Pool full, destroying connection")
263
+ self._destroy_connection(conn)
264
+ else:
265
+ # Destroy excess connections
266
+ self._destroy_connection(conn)
267
+
268
+ def get_statistics(self) -> Dict[str, Any]:
269
+ """
270
+ Get pool statistics.
271
+
272
+ Returns:
273
+ Dictionary with pool statistics
274
+ """
275
+ return {
276
+ **self._stats,
277
+ "pool_size": self._pool.qsize(),
278
+ "active_connections": self._active_connections,
279
+ "min_size": self.min_size,
280
+ "max_size": self.max_size
281
+ }
282
+
283
+ def close(self):
284
+ """
285
+ Close all connections and shut down the pool.
286
+ """
287
+ if self._closed:
288
+ return
289
+
290
+ self._closed = True
291
+ logger.info("Closing connection pool...")
292
+
293
+ # Close all pooled connections
294
+ while not self._pool.empty():
295
+ try:
296
+ conn = self._pool.get(block=False)
297
+ self._destroy_connection(conn)
298
+ except Empty:
299
+ break
300
+
301
+ logger.info(
302
+ f"Connection pool closed. Stats: {self.get_statistics()}"
303
+ )
304
+
305
+ def __enter__(self):
306
+ """Context manager entry."""
307
+ return self
308
+
309
+ def __exit__(self, exc_type, exc_val, exc_tb):
310
+ """Context manager exit."""
311
+ self.close()
312
+ return False
313
+
314
+
315
+ # Global singleton pool (lazy-initialized)
316
+ _global_pool: Optional[IRISConnectionPool] = None
317
+ _pool_lock = threading.Lock()
318
+
319
+
320
+ def get_global_pool(
321
+ min_size: int = 10,
322
+ max_size: int = 50,
323
+ **kwargs
324
+ ) -> IRISConnectionPool:
325
+ """
326
+ Get or create the global connection pool singleton.
327
+
328
+ Args:
329
+ min_size: Minimum pool size (only used on first creation)
330
+ max_size: Maximum pool size (only used on first creation)
331
+ **kwargs: Additional arguments passed to IRISConnectionPool
332
+
333
+ Returns:
334
+ Global IRISConnectionPool instance
335
+ """
336
+ global _global_pool
337
+
338
+ if _global_pool is None:
339
+ with _pool_lock:
340
+ if _global_pool is None:
341
+ _global_pool = IRISConnectionPool(
342
+ min_size=min_size,
343
+ max_size=max_size,
344
+ **kwargs
345
+ )
346
+ logger.info("Created global IRIS connection pool")
347
+
348
+ return _global_pool
349
+
350
+
351
+ def close_global_pool():
352
+ """Close the global connection pool if it exists."""
353
+ global _global_pool
354
+
355
+ if _global_pool is not None:
356
+ with _pool_lock:
357
+ if _global_pool is not None:
358
+ _global_pool.close()
359
+ _global_pool = None
360
+ logger.info("Closed global IRIS connection pool")
@@ -0,0 +1,71 @@
1
+ """
2
+ Clean IRIS Database Connector
3
+ Uses DBAPI primarily, JDBC only as emergency fallback.
4
+ """
5
+
6
+ import logging
7
+ from typing import Any, Dict, Optional
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def get_iris_connection(
13
+ config: Optional[Dict[str, Any]] = None, prefer_dbapi: bool = True
14
+ ) -> Any:
15
+ """
16
+ Get an IRIS database connection, preferring DBAPI by default.
17
+
18
+ Args:
19
+ config: Optional configuration dictionary
20
+ prefer_dbapi: Whether to prefer DBAPI over JDBC (default: True)
21
+
22
+ Returns:
23
+ Database connection object
24
+
25
+ Raises:
26
+ Exception: If both DBAPI and JDBC fail
27
+ """
28
+ # Always try DBAPI first
29
+ try:
30
+ from common.iris_dbapi_connector import get_iris_dbapi_connection
31
+
32
+ conn = get_iris_dbapi_connection()
33
+
34
+ # Validate the connection handle
35
+ if conn is None:
36
+ raise IRISConnectionError("DBAPI connection returned NULL handle")
37
+
38
+ logger.info("✅ Using DBAPI connection")
39
+ return conn
40
+ except Exception as dbapi_error:
41
+ logger.error(f"❌ DBAPI connection failed: {dbapi_error}")
42
+
43
+ if not prefer_dbapi:
44
+ # Only fall back to JDBC if explicitly requested
45
+ try:
46
+ from common.iris_connection_manager import get_iris_jdbc_connection
47
+
48
+ conn = get_iris_jdbc_connection(config)
49
+
50
+ # Validate the connection handle
51
+ if conn is None:
52
+ raise IRISConnectionError("JDBC connection returned NULL handle")
53
+
54
+ logger.warning(
55
+ "⚠️ Falling back to JDBC connection - this indicates a DBAPI problem!"
56
+ )
57
+ return conn
58
+ except Exception as jdbc_error:
59
+ logger.error(f"❌ JDBC fallback also failed: {jdbc_error}")
60
+ raise IRISConnectionError(
61
+ f"Both DBAPI and JDBC connections failed. DBAPI: {dbapi_error}, JDBC: {jdbc_error}"
62
+ )
63
+ else:
64
+ # If DBAPI fails and we prefer it, this is a critical error
65
+ raise IRISConnectionError(
66
+ f"DBAPI connection failed and fallback disabled: {dbapi_error}"
67
+ )
68
+
69
+
70
+ class IRISConnectionError(Exception):
71
+ """Custom exception for IRIS connection errors."""