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,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
|