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,323 @@
1
+ """
2
+ Connector for InterSystems IRIS using Python DBAPI
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ import subprocess
8
+ import re
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def auto_detect_iris_port():
14
+ """
15
+ Auto-detect running IRIS instance and its SuperServer port.
16
+
17
+ Checks in priority order:
18
+ 1. Docker containers with IRIS (port 1972 mapped)
19
+ 2. Native IRIS instances via 'iris list' command
20
+
21
+ Returns:
22
+ int: SuperServer port of first accessible instance, or None if none found.
23
+ """
24
+ # Priority 1: Check for Docker IRIS containers
25
+ try:
26
+ result = subprocess.run(
27
+ ["docker", "ps", "--format", "{{.Names}}\t{{.Ports}}"],
28
+ capture_output=True,
29
+ text=True,
30
+ timeout=5
31
+ )
32
+
33
+ if result.returncode == 0:
34
+ # Look for IRIS containers with port mappings
35
+ for line in result.stdout.split('\n'):
36
+ if 'iris' in line.lower() and '1972' in line:
37
+ # Parse port mapping like "0.0.0.0:1972->1972/tcp"
38
+ # Extract the external port (first number)
39
+ match = re.search(r'0\.0\.0\.0:(\d+)->1972/tcp', line)
40
+ if match:
41
+ port = int(match.group(1))
42
+ logger.info(f"✅ Auto-detected Docker IRIS on port {port}")
43
+ return port
44
+
45
+ except (FileNotFoundError, subprocess.TimeoutExpired):
46
+ logger.debug("Docker not available or timed out, trying native IRIS")
47
+ except Exception as e:
48
+ logger.debug(f"Docker check failed: {e}")
49
+
50
+ # Priority 2: Check native IRIS instances
51
+ try:
52
+ result = subprocess.run(
53
+ ["iris", "list"],
54
+ capture_output=True,
55
+ text=True,
56
+ timeout=5
57
+ )
58
+
59
+ if result.returncode != 0:
60
+ logger.warning(f"'iris list' command failed with exit code {result.returncode}")
61
+ return None
62
+
63
+ # Parse output for running instances
64
+ # Format: "status: running, since ..."
65
+ # Then next section: "SuperServers: <port>"
66
+ lines = result.stdout.split('\n')
67
+
68
+ for i, line in enumerate(lines):
69
+ if 'status:' in line and 'running' in line:
70
+ # Found a running instance, look for SuperServers port in next few lines
71
+ for j in range(i+1, min(i+5, len(lines))):
72
+ if 'SuperServers:' in lines[j]:
73
+ # Extract port number
74
+ match = re.search(r'SuperServers:\s+(\d+)', lines[j])
75
+ if match:
76
+ port = int(match.group(1))
77
+ logger.info(f"✅ Auto-detected native IRIS on SuperServer port {port}")
78
+ return port
79
+
80
+ logger.warning("No running IRIS instances found")
81
+ return None
82
+
83
+ except FileNotFoundError:
84
+ logger.warning("'iris' command not found in PATH - cannot auto-detect")
85
+ return None
86
+ except subprocess.TimeoutExpired:
87
+ logger.warning("'iris list' command timed out")
88
+ return None
89
+ except Exception as e:
90
+ logger.warning(f"Failed to auto-detect IRIS port: {e}")
91
+ return None
92
+
93
+
94
+ def _get_iris_dbapi_module():
95
+ """
96
+ Attempts to import and return the appropriate IRIS DBAPI module.
97
+
98
+ Based on PyPI documentation for intersystems-irispython package:
99
+ - The main import is 'import iris'
100
+ - DBAPI functionality is accessed through the iris module
101
+ - The package provides both native connections and DBAPI interface
102
+
103
+ Returns:
104
+ The IRIS DBAPI module if successfully imported, None otherwise.
105
+ """
106
+ try:
107
+ import iris as iris_dbapi
108
+
109
+ # Check if iris_dbapi module has _DBAPI submodule with connect method
110
+ if hasattr(iris_dbapi, "_DBAPI") and hasattr(iris_dbapi._DBAPI, "connect"):
111
+ # The _DBAPI submodule provides the DBAPI interface
112
+ logger.info("Successfully imported 'iris' module with DBAPI interface")
113
+ return iris_dbapi._DBAPI
114
+ elif hasattr(iris_dbapi, "connect"):
115
+ # The iris_dbapi module itself provides the DBAPI interface
116
+ logger.info("Successfully imported 'iris' module with DBAPI interface")
117
+ return iris_dbapi
118
+ else:
119
+ logger.warning(
120
+ "'iris' module imported but doesn't appear to have DBAPI interface (no 'connect' method)"
121
+ )
122
+ except (ImportError, AttributeError) as e:
123
+ logger.error(f"Failed to import 'iris' module (circular import issue): {e}")
124
+
125
+ # Fallback to direct iris import for older installations
126
+ try:
127
+ import iris
128
+
129
+ if hasattr(iris, "connect"):
130
+ logger.info(
131
+ "Successfully imported 'iris' module with DBAPI interface (fallback)"
132
+ )
133
+ return iris
134
+ else:
135
+ logger.warning(
136
+ "'iris' module imported but doesn't appear to have DBAPI interface (no 'connect' method)"
137
+ )
138
+ except ImportError as e2:
139
+ logger.warning(f"Failed to import 'iris' module as fallback: {e2}")
140
+
141
+ # All import attempts failed
142
+ logger.error(
143
+ "InterSystems IRIS DBAPI module could not be imported. "
144
+ "The 'iris' module was found but doesn't have the expected 'connect' method. "
145
+ "Please ensure the 'intersystems-irispython' package is installed correctly. "
146
+ "DBAPI connections will not be available."
147
+ )
148
+ return None
149
+
150
+
151
+ def get_iris_dbapi_connection():
152
+ """
153
+ Establishes a connection to InterSystems IRIS using direct iris.connect().
154
+
155
+ This replaces the problematic DBAPI connection that had SSL errors.
156
+ Uses direct iris.connect() which is proven to work reliably.
157
+
158
+ Reads connection parameters from environment variables:
159
+ - IRIS_HOST
160
+ - IRIS_PORT
161
+ - IRIS_NAMESPACE
162
+ - IRIS_USER
163
+ - IRIS_PASSWORD
164
+
165
+ Returns:
166
+ A direct IRIS connection object or None if connection fails.
167
+ """
168
+ # Use direct iris import instead of DBAPI
169
+ try:
170
+ import iris
171
+ except ImportError as e:
172
+ logger.error(f"Cannot import iris module: {e}")
173
+ return None
174
+
175
+ # Get connection parameters from environment with auto-detection fallback
176
+ host = os.environ.get("IRIS_HOST", "localhost")
177
+
178
+ # Auto-detect port if not set in environment
179
+ port_env = os.environ.get("IRIS_PORT")
180
+ if port_env:
181
+ port = int(port_env)
182
+ logger.info(f"Using IRIS port from environment: {port}")
183
+ else:
184
+ port = auto_detect_iris_port()
185
+ if port is None:
186
+ logger.warning("Could not auto-detect IRIS port, falling back to default 1972")
187
+ port = 1972
188
+
189
+ namespace = os.environ.get("IRIS_NAMESPACE", "USER")
190
+ user = os.environ.get("IRIS_USER", "_SYSTEM")
191
+ password = os.environ.get("IRIS_PASSWORD", "SYS")
192
+
193
+ # Retry connection with exponential backoff for transient errors
194
+ max_retries = 3
195
+ retry_delay = 0.5 # Start with 500ms delay
196
+
197
+ for attempt in range(max_retries):
198
+ try:
199
+ if attempt > 0:
200
+ import time
201
+ logger.info(f"Retry attempt {attempt + 1}/{max_retries} after {retry_delay}s delay")
202
+ time.sleep(retry_delay)
203
+ retry_delay *= 2 # Exponential backoff
204
+
205
+ logger.info(
206
+ f"Attempting IRIS connection to {host}:{port}/{namespace} as user {user}"
207
+ )
208
+
209
+ # Use direct iris.connect() - this avoids SSL issues
210
+ conn = iris.connect(host, port, namespace, user, password)
211
+
212
+ # Validate the connection
213
+ if conn is None:
214
+ logger.error("Direct IRIS connection failed: connection is None")
215
+ if attempt < max_retries - 1:
216
+ continue
217
+ return None
218
+
219
+ # Test the connection with a simple query
220
+ try:
221
+ cursor = conn.cursor()
222
+ if cursor is None:
223
+ logger.error("Direct IRIS connection failed: cursor is None")
224
+ conn.close()
225
+ if attempt < max_retries - 1:
226
+ continue
227
+ return None
228
+
229
+ cursor.execute("SELECT 1")
230
+ result = cursor.fetchone()
231
+ cursor.close()
232
+
233
+ if result is None:
234
+ logger.error("Direct IRIS connection failed: test query returned None")
235
+ conn.close()
236
+ if attempt < max_retries - 1:
237
+ continue
238
+ return None
239
+
240
+ except Exception as test_e:
241
+ logger.error(f"Direct IRIS connection validation failed: {test_e}")
242
+ try:
243
+ conn.close()
244
+ except:
245
+ pass
246
+ if attempt < max_retries - 1:
247
+ continue
248
+ return None
249
+
250
+ logger.info("✅ Successfully connected to IRIS using direct iris.connect()")
251
+ return conn
252
+
253
+ except Exception as e:
254
+ logger.error(f"Direct IRIS connection failed (attempt {attempt + 1}/{max_retries}): {e}")
255
+ if attempt < max_retries - 1:
256
+ continue
257
+ return None
258
+
259
+ return None
260
+
261
+
262
+ # Lazy-loaded DBAPI module - initialized only when needed
263
+ _cached_irisdbapi = None
264
+
265
+
266
+ def get_iris_dbapi_module():
267
+ """
268
+ Get the IRIS DBAPI module with lazy loading to avoid circular imports.
269
+
270
+ This function caches the module after first successful import to avoid
271
+ repeated import attempts.
272
+
273
+ Returns:
274
+ The IRIS DBAPI module if available, None otherwise.
275
+ """
276
+ global _cached_irisdbapi
277
+
278
+ if _cached_irisdbapi is None:
279
+ _cached_irisdbapi = _get_iris_dbapi_module()
280
+
281
+ return _cached_irisdbapi
282
+
283
+
284
+ # For backward compatibility, provide irisdbapi as a property-like access
285
+ @property
286
+ def irisdbapi():
287
+ """Backward compatibility property for accessing the IRIS DBAPI module."""
288
+ return get_iris_dbapi_module()
289
+
290
+
291
+ # Make irisdbapi available as module attribute through __getattr__
292
+ def __getattr__(name):
293
+ """Module-level attribute access for backward compatibility."""
294
+ if name == "irisdbapi":
295
+ return get_iris_dbapi_module()
296
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
297
+
298
+
299
+ if __name__ == "__main__":
300
+ # Basic test for the connection
301
+ # Ensure environment variables are set (e.g., in a .env file or system-wide)
302
+ # Example:
303
+ # export IRIS_HOST="your_iris_host"
304
+ # export IRIS_PORT="1972"
305
+ # export IRIS_NAMESPACE="USER"
306
+ # export IRIS_USER="your_user"
307
+ # export IRIS_PASSWORD="your_password"
308
+ logging.basicConfig(level=logging.INFO)
309
+ connection = get_iris_dbapi_connection()
310
+ if connection:
311
+ try:
312
+ cursor = connection.cursor()
313
+ cursor.execute("SELECT %Version FROM %SYSTEM.Version")
314
+ version = cursor.fetchone()
315
+ logger.info(f"IRIS Version (DBAPI): {version[0]}")
316
+ cursor.close()
317
+ except Exception as e:
318
+ logger.error(f"Error during DBAPI test query: {e}")
319
+ finally:
320
+ connection.close()
321
+ logger.info("DBAPI connection closed.")
322
+ else:
323
+ logger.warning("DBAPI connection could not be established for testing.")
@@ -0,0 +1,195 @@
1
+ """
2
+ IRIS index creation utilities with proper error handling.
3
+ """
4
+
5
+ import logging
6
+ from typing import List, Optional
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def create_index_if_not_exists(
12
+ cursor,
13
+ index_name: str,
14
+ table_name: str,
15
+ columns: str,
16
+ index_type: Optional[str] = None,
17
+ ):
18
+ """
19
+ Create an index if it doesn't already exist.
20
+
21
+ Args:
22
+ cursor: Database cursor
23
+ index_name: Name of the index
24
+ table_name: Name of the table
25
+ columns: Column specification for the index
26
+ index_type: Optional index type (e.g., "AS HNSW(M=16, efConstruction=200, Distance='COSINE')")
27
+ """
28
+ try:
29
+ # Check if index already exists
30
+ check_sql = """
31
+ SELECT COUNT(*) FROM INFORMATION_SCHEMA.INDEXES
32
+ WHERE INDEX_NAME = ? AND TABLE_NAME = ?
33
+ """
34
+ cursor.execute(
35
+ check_sql, (index_name, table_name.split(".")[-1])
36
+ ) # Remove schema prefix for check
37
+ result = cursor.fetchone()
38
+
39
+ if result and result[0] > 0:
40
+ logger.debug(f"Index {index_name} already exists on {table_name}")
41
+ return True
42
+
43
+ # Create the index
44
+ if index_type:
45
+ create_sql = (
46
+ f"CREATE INDEX {index_name} ON {table_name} ({columns}) {index_type}"
47
+ )
48
+ else:
49
+ create_sql = f"CREATE INDEX {index_name} ON {table_name} ({columns})"
50
+
51
+ logger.info(f"Creating index: {create_sql}")
52
+ cursor.execute(create_sql)
53
+ logger.info(f"Successfully created index {index_name}")
54
+ return True
55
+
56
+ except Exception as e:
57
+ error_str = str(e).lower()
58
+
59
+ # Check if error is due to index already existing
60
+ if any(
61
+ indicator in error_str
62
+ for indicator in [
63
+ "already exists",
64
+ "duplicate",
65
+ "index exists",
66
+ "name already used",
67
+ ]
68
+ ):
69
+ logger.debug(f"Index {index_name} already exists (caught exception): {e}")
70
+ return True
71
+ else:
72
+ logger.error(f"Failed to create index {index_name}: {e}")
73
+ return False
74
+
75
+
76
+ def create_indexes_from_sql_file(cursor, sql_file_path: str) -> List[str]:
77
+ """
78
+ Create indexes from SQL file with proper error handling.
79
+
80
+ Args:
81
+ cursor: Database cursor
82
+ sql_file_path: Path to SQL file containing index creation statements
83
+
84
+ Returns:
85
+ List of failed index creation statements
86
+ """
87
+ failed_statements = []
88
+
89
+ try:
90
+ with open(sql_file_path, "r") as f:
91
+ sql_content = f.read()
92
+
93
+ # Split into individual statements
94
+ statements = [stmt.strip() for stmt in sql_content.split(";") if stmt.strip()]
95
+
96
+ for statement in statements:
97
+ if statement.upper().startswith("CREATE INDEX"):
98
+ try:
99
+ # Replace "CREATE INDEX IF NOT EXISTS" with "CREATE INDEX"
100
+ statement = statement.replace("IF NOT EXISTS", "").replace(
101
+ "if not exists", ""
102
+ )
103
+
104
+ logger.debug(f"Executing: {statement}")
105
+ cursor.execute(statement)
106
+ logger.debug(f"Successfully executed: {statement[:50]}...")
107
+
108
+ except Exception as e:
109
+ error_str = str(e).lower()
110
+
111
+ # Check if error is due to index already existing
112
+ if any(
113
+ indicator in error_str
114
+ for indicator in [
115
+ "already exists",
116
+ "duplicate",
117
+ "index exists",
118
+ "name already used",
119
+ ]
120
+ ):
121
+ logger.debug(
122
+ f"Index already exists (ignored): {statement[:50]}..."
123
+ )
124
+ else:
125
+ logger.warning(
126
+ f"Failed to execute statement: {statement[:50]}... Error: {e}"
127
+ )
128
+ failed_statements.append(statement)
129
+ else:
130
+ # Execute non-index statements normally
131
+ if statement and not statement.startswith("--"):
132
+ try:
133
+ cursor.execute(statement)
134
+ except Exception as e:
135
+ logger.warning(
136
+ f"Failed to execute statement: {statement[:50]}... Error: {e}"
137
+ )
138
+ failed_statements.append(statement)
139
+
140
+ except Exception as e:
141
+ logger.error(f"Failed to read SQL file {sql_file_path}: {e}")
142
+ failed_statements.append(f"Failed to read file: {sql_file_path}")
143
+
144
+ return failed_statements
145
+
146
+
147
+ def ensure_schema_indexes(cursor, schema_name: str = "RAG") -> bool:
148
+ """
149
+ Ensure all required indexes exist for the RAG schema.
150
+
151
+ Args:
152
+ cursor: Database cursor
153
+ schema_name: Name of the schema
154
+
155
+ Returns:
156
+ True if all indexes were created successfully, False otherwise
157
+ """
158
+ indexes = [
159
+ # SourceDocuments indexes
160
+ ("idx_source_docs_id", f"{schema_name}.SourceDocuments", "doc_id"),
161
+ (
162
+ "idx_hnsw_source_embedding",
163
+ f"{schema_name}.SourceDocuments",
164
+ "embedding",
165
+ "AS HNSW(M=16, efConstruction=200, Distance='COSINE')",
166
+ ),
167
+ ("idx_source_docs_created", f"{schema_name}.SourceDocuments", "created_at"),
168
+ # DocumentChunks indexes
169
+ ("idx_chunks_doc_id", f"{schema_name}.DocumentChunks", "doc_id"),
170
+ ("idx_chunks_type", f"{schema_name}.DocumentChunks", "chunk_type"),
171
+ (
172
+ "idx_hnsw_chunk_embedding",
173
+ f"{schema_name}.DocumentChunks",
174
+ "chunk_embedding",
175
+ "AS HNSW(M=16, efConstruction=200, Distance='COSINE')",
176
+ ),
177
+ ]
178
+
179
+ success_count = 0
180
+ total_count = len(indexes)
181
+
182
+ for index_spec in indexes:
183
+ if len(index_spec) == 3:
184
+ index_name, table_name, columns = index_spec
185
+ index_type = None
186
+ else:
187
+ index_name, table_name, columns, index_type = index_spec
188
+
189
+ if create_index_if_not_exists(
190
+ cursor, index_name, table_name, columns, index_type
191
+ ):
192
+ success_count += 1
193
+
194
+ logger.info(f"Successfully created/verified {success_count}/{total_count} indexes")
195
+ return success_count == total_count