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.
- adapters/rag_templates_bridge.py +578 -0
- common/__init__.py +1 -0
- common/batch_utils.py +294 -0
- common/config.py +58 -0
- common/connection_manager.py +242 -0
- common/connection_pool.py +445 -0
- common/database.py +141 -0
- common/database_schema_manager.py +289 -0
- common/db_init_with_indexes.py +181 -0
- common/db_vector_utils.py +181 -0
- common/environment_manager.py +220 -0
- common/huggingface_utils.py +206 -0
- common/iris_client.py +92 -0
- common/iris_connection_manager.py +411 -0
- common/iris_connection_pool.py +360 -0
- common/iris_connector.py +71 -0
- common/iris_dbapi_connector.py +323 -0
- common/iris_index_utils.py +195 -0
- common/iris_sql_utils.py +704 -0
- common/iris_stream_reader.py +166 -0
- common/llm_cache_config.py +308 -0
- common/llm_cache_iris.py +671 -0
- common/llm_cache_manager.py +911 -0
- common/utils.py +559 -0
- common/vector_sql_utils.py +584 -0
- evaluation_framework/__init__.py +66 -0
- evaluation_framework/biomedical_question_generator.py +826 -0
- evaluation_framework/comparative_analysis_system.py +1173 -0
- evaluation_framework/empirical_reporting.py +1298 -0
- evaluation_framework/evaluation_orchestrator.py +1123 -0
- evaluation_framework/execute_production_evaluation.py +382 -0
- evaluation_framework/pmc_data_pipeline.py +1332 -0
- evaluation_framework/production_evaluation_config.py +353 -0
- evaluation_framework/ragas_metrics_framework.py +1013 -0
- evaluation_framework/real_production_evaluation.py +544 -0
- evaluation_framework/run_10k_evaluation.py +175 -0
- evaluation_framework/simple_test.py +364 -0
- evaluation_framework/statistical_evaluation_methodology.py +1016 -0
- evaluation_framework/test_framework.py +281 -0
- evaluation_framework/test_iris_connectivity.py +244 -0
- evaluation_framework/test_small_scale_evaluation.py +432 -0
- evaluation_framework/true_e2e_evaluation.py +639 -0
- evaluation_framework/visualization_engine.py +585 -0
- iris_rag/__init__.py +284 -0
- iris_rag/config/default_config.yaml +295 -0
- iris_vector_rag-0.2.1.dist-info/METADATA +499 -0
- iris_vector_rag-0.2.1.dist-info/RECORD +50 -0
- iris_vector_rag-0.2.1.dist-info/WHEEL +5 -0
- iris_vector_rag-0.2.1.dist-info/entry_points.txt +2 -0
- 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")
|
common/iris_connector.py
ADDED
|
@@ -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."""
|