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,584 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vector SQL Utilities for IRIS Database
|
|
3
|
+
|
|
4
|
+
This module encapsulates workarounds for InterSystems IRIS SQL vector operations limitations.
|
|
5
|
+
It provides helper functions that RAG pipelines can use to safely construct SQL queries
|
|
6
|
+
with vector operations.
|
|
7
|
+
|
|
8
|
+
Key IRIS SQL Limitations Addressed:
|
|
9
|
+
|
|
10
|
+
1. TO_VECTOR() Function Rejects Parameter Markers:
|
|
11
|
+
The TO_VECTOR() function does not accept parameter markers (?, :param, or :%qpar),
|
|
12
|
+
which are standard in SQL for safe query parameterization.
|
|
13
|
+
|
|
14
|
+
2. TOP/FETCH FIRST Clauses Cannot Be Parameterized:
|
|
15
|
+
The TOP and FETCH FIRST clauses, essential for limiting results in vector similarity
|
|
16
|
+
searches, do not accept parameter markers.
|
|
17
|
+
|
|
18
|
+
3. Client Drivers Rewrite Literals:
|
|
19
|
+
Python, JDBC, and other client drivers replace embedded literals with :%qpar(n)
|
|
20
|
+
even when no parameter list is supplied, creating misleading parse errors.
|
|
21
|
+
|
|
22
|
+
These limitations force developers to use string interpolation instead of parameterized
|
|
23
|
+
queries, which introduces potential security risks. This module provides functions to
|
|
24
|
+
validate inputs and safely construct SQL queries using string interpolation.
|
|
25
|
+
|
|
26
|
+
SAFE VECTOR UTILITIES:
|
|
27
|
+
- build_safe_vector_dot_sql(): Safe single-parameter query builder
|
|
28
|
+
- execute_safe_vector_search(): Safe execution with single vector parameter
|
|
29
|
+
|
|
30
|
+
QUARANTINED UTILITIES (pending IRIS fix):
|
|
31
|
+
- format_vector_search_sql(): Deprecated - see docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
32
|
+
- format_vector_search_sql_with_params(): Deprecated - see docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
33
|
+
- execute_vector_search_with_params(): Deprecated - see docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
34
|
+
|
|
35
|
+
For more details on these limitations, see docs/IRIS_SQL_VECTOR_OPERATIONS.md
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import logging
|
|
39
|
+
import re
|
|
40
|
+
from typing import Any, List, Tuple
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger(__name__)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def validate_vector_string(vector_string: str) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Validates that a vector string contains a valid vector format.
|
|
48
|
+
Allows negative numbers and scientific notation while preventing SQL injection.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
vector_string: The vector string to validate, typically in format "[0.1,-0.2,...]"
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
bool: True if valid vector format, False otherwise
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> validate_vector_string("[0.1,-0.2,3.5e-4]")
|
|
58
|
+
True
|
|
59
|
+
>>> validate_vector_string("'; DROP TABLE users; --")
|
|
60
|
+
False
|
|
61
|
+
"""
|
|
62
|
+
# Check basic structure
|
|
63
|
+
stripped = vector_string.strip()
|
|
64
|
+
if not (stripped.startswith("[") and stripped.endswith("]")):
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
# Extract content between brackets
|
|
68
|
+
content = stripped[1:-1]
|
|
69
|
+
if not content.strip():
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
# Validate each number
|
|
73
|
+
parts = content.split(",")
|
|
74
|
+
for part in parts:
|
|
75
|
+
try:
|
|
76
|
+
float(part.strip())
|
|
77
|
+
except ValueError:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
# Check for SQL injection patterns
|
|
81
|
+
if re.search(
|
|
82
|
+
r"(DROP|DELETE|INSERT|UPDATE|SELECT|;|--)", vector_string, re.IGNORECASE
|
|
83
|
+
):
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def validate_top_k(top_k: Any) -> bool:
|
|
90
|
+
"""
|
|
91
|
+
Validates that top_k is a positive integer.
|
|
92
|
+
This is important for security when using string interpolation.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
top_k: The value to validate
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
bool: True if top_k is a positive integer, False otherwise
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> validate_top_k(10)
|
|
102
|
+
True
|
|
103
|
+
>>> validate_top_k(0)
|
|
104
|
+
False
|
|
105
|
+
>>> validate_top_k("10; DROP TABLE users; --")
|
|
106
|
+
False
|
|
107
|
+
"""
|
|
108
|
+
if not isinstance(top_k, int):
|
|
109
|
+
return False
|
|
110
|
+
return top_k > 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def format_vector_search_sql(
|
|
114
|
+
table_name: str,
|
|
115
|
+
vector_column: str,
|
|
116
|
+
vector_string: str,
|
|
117
|
+
embedding_dim: int,
|
|
118
|
+
top_k: int,
|
|
119
|
+
id_column: str = "doc_id",
|
|
120
|
+
content_column: str = "text_content",
|
|
121
|
+
additional_where: str = None,
|
|
122
|
+
) -> str:
|
|
123
|
+
"""
|
|
124
|
+
@deprecated - Quarantined pending IRIS fix
|
|
125
|
+
See docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
126
|
+
|
|
127
|
+
Constructs a SQL query for vector search using careful string concatenation
|
|
128
|
+
to avoid IRIS driver auto-parameterization issues.
|
|
129
|
+
|
|
130
|
+
IRIS-specific workaround: The IRIS driver converts embedded literals to :%qpar(n)
|
|
131
|
+
parameter markers automatically, but TO_VECTOR() and TOP clauses cannot accept
|
|
132
|
+
parameter markers. This function constructs SQL to avoid triggering the
|
|
133
|
+
auto-parameterization.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
table_name: The name of the table to search
|
|
137
|
+
vector_column: The name of the column containing vector embeddings
|
|
138
|
+
vector_string: The vector string to search for, in format "[0.1,0.2,...]"
|
|
139
|
+
embedding_dim: The dimension of the embedding vectors
|
|
140
|
+
top_k: The number of results to return
|
|
141
|
+
id_column: The name of the ID column (default: "doc_id")
|
|
142
|
+
content_column: The name of the content column (default: "text_content")
|
|
143
|
+
Set to None if you don't want to include content in results
|
|
144
|
+
additional_where: Additional WHERE clause conditions (default: None)
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
str: The formatted SQL query string
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
ValueError: If any of the inputs fail validation
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
>>> format_vector_search_sql(
|
|
154
|
+
... "SourceDocuments",
|
|
155
|
+
... "embedding",
|
|
156
|
+
... "[0.1,0.2,0.3]",
|
|
157
|
+
... 768,
|
|
158
|
+
... 10,
|
|
159
|
+
... "doc_id",
|
|
160
|
+
... "text_content"
|
|
161
|
+
... )
|
|
162
|
+
'SELECT TOP 10 doc_id, text_content,
|
|
163
|
+
VECTOR_COSINE(embedding, TO_VECTOR('[0.1,0.2,0.3]', FLOAT, 768)) AS score
|
|
164
|
+
FROM SourceDocuments
|
|
165
|
+
WHERE embedding IS NOT NULL
|
|
166
|
+
ORDER BY score DESC'
|
|
167
|
+
"""
|
|
168
|
+
# Validate table_name to prevent SQL injection (allow schema.table format)
|
|
169
|
+
if not re.match(r"^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)?$", table_name):
|
|
170
|
+
raise ValueError(f"Invalid table name: {table_name}")
|
|
171
|
+
|
|
172
|
+
# Validate column names to prevent SQL injection
|
|
173
|
+
for col in [vector_column, id_column]:
|
|
174
|
+
if not re.match(r"^[a-zA-Z0-9_]+$", col):
|
|
175
|
+
raise ValueError(f"Invalid column name: {col}")
|
|
176
|
+
|
|
177
|
+
if content_column and not re.match(r"^[a-zA-Z0-9_]+$", content_column):
|
|
178
|
+
raise ValueError(f"Invalid content column name: {content_column}")
|
|
179
|
+
|
|
180
|
+
# Validate vector_string
|
|
181
|
+
if not validate_vector_string(vector_string):
|
|
182
|
+
raise ValueError(f"Invalid vector string: {vector_string}")
|
|
183
|
+
|
|
184
|
+
# Validate embedding_dim
|
|
185
|
+
if not isinstance(embedding_dim, int) or embedding_dim <= 0:
|
|
186
|
+
raise ValueError(f"Invalid embedding dimension: {embedding_dim}")
|
|
187
|
+
|
|
188
|
+
# Validate top_k
|
|
189
|
+
if not validate_top_k(top_k):
|
|
190
|
+
raise ValueError(f"Invalid top_k value: {top_k}")
|
|
191
|
+
|
|
192
|
+
# Convert values to strings to avoid auto-parameterization
|
|
193
|
+
top_k_str = str(top_k)
|
|
194
|
+
embedding_dim_str = str(embedding_dim)
|
|
195
|
+
|
|
196
|
+
# Construct SQL using string concatenation to avoid f-string literal detection
|
|
197
|
+
select_parts = ["SELECT TOP ", top_k_str, " ", id_column]
|
|
198
|
+
if content_column:
|
|
199
|
+
select_parts.extend([", ", content_column])
|
|
200
|
+
|
|
201
|
+
# Construct TO_VECTOR call carefully to avoid parameter detection
|
|
202
|
+
# Use FLOAT datatype to match how test data was created (see tests/fixtures/embedding_generator.py:247)
|
|
203
|
+
vector_func_parts = [
|
|
204
|
+
", VECTOR_COSINE(",
|
|
205
|
+
vector_column,
|
|
206
|
+
", TO_VECTOR('",
|
|
207
|
+
vector_string,
|
|
208
|
+
"', FLOAT, ",
|
|
209
|
+
embedding_dim_str,
|
|
210
|
+
")) AS score",
|
|
211
|
+
]
|
|
212
|
+
select_parts.extend(vector_func_parts)
|
|
213
|
+
select_clause = "".join(select_parts)
|
|
214
|
+
|
|
215
|
+
# Construct the WHERE clause
|
|
216
|
+
where_parts = ["WHERE ", vector_column, " IS NOT NULL"]
|
|
217
|
+
if additional_where:
|
|
218
|
+
where_parts.extend([" AND (", additional_where, ")"])
|
|
219
|
+
where_clause = "".join(where_parts)
|
|
220
|
+
|
|
221
|
+
# Construct the full SQL query using string concatenation
|
|
222
|
+
sql_parts = [
|
|
223
|
+
select_clause,
|
|
224
|
+
" FROM ",
|
|
225
|
+
table_name,
|
|
226
|
+
" ",
|
|
227
|
+
where_clause,
|
|
228
|
+
" ORDER BY score DESC",
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
return "".join(sql_parts)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def format_vector_search_sql_with_params(
|
|
235
|
+
table_name: str,
|
|
236
|
+
vector_column: str,
|
|
237
|
+
embedding_dim: int,
|
|
238
|
+
top_k: int,
|
|
239
|
+
id_column: str = "doc_id",
|
|
240
|
+
content_column: str = "text_content",
|
|
241
|
+
additional_where: str = None,
|
|
242
|
+
) -> str:
|
|
243
|
+
"""
|
|
244
|
+
@deprecated - Quarantined pending IRIS fix
|
|
245
|
+
See docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
246
|
+
|
|
247
|
+
Constructs a SQL query for vector search using parameter placeholders.
|
|
248
|
+
Uses string concatenation to avoid IRIS driver auto-parameterization issues.
|
|
249
|
+
|
|
250
|
+
IRIS-specific workaround: Even when using ? placeholders, the IRIS driver
|
|
251
|
+
can still auto-parameterize literals in f-strings. This function uses
|
|
252
|
+
string concatenation to avoid triggering the auto-parameterization.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
table_name: The name of the table to search
|
|
256
|
+
vector_column: The name of the column containing vector embeddings
|
|
257
|
+
embedding_dim: The dimension of the embedding vectors (for documentation)
|
|
258
|
+
top_k: The number of results to return
|
|
259
|
+
id_column: The name of the ID column (default: "doc_id")
|
|
260
|
+
content_column: The name of the content column (default: "text_content")
|
|
261
|
+
additional_where: Additional WHERE clause conditions (default: None)
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
str: The formatted SQL query string with ? placeholder
|
|
265
|
+
"""
|
|
266
|
+
# Validate inputs (reuse existing validation)
|
|
267
|
+
if not re.match(r"^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)?$", table_name):
|
|
268
|
+
raise ValueError(f"Invalid table name: {table_name}")
|
|
269
|
+
|
|
270
|
+
for col in [vector_column, id_column]:
|
|
271
|
+
if not re.match(r"^[a-zA-Z0-9_]+$", col):
|
|
272
|
+
raise ValueError(f"Invalid column name: {col}")
|
|
273
|
+
|
|
274
|
+
if content_column and not re.match(r"^[a-zA-Z0-9_]+$", content_column):
|
|
275
|
+
raise ValueError(f"Invalid content column name: {content_column}")
|
|
276
|
+
|
|
277
|
+
if not validate_top_k(top_k):
|
|
278
|
+
raise ValueError(f"Invalid top_k value: {top_k}")
|
|
279
|
+
|
|
280
|
+
# Convert values to strings to avoid auto-parameterization
|
|
281
|
+
top_k_str = str(top_k)
|
|
282
|
+
embedding_dim_str = str(embedding_dim)
|
|
283
|
+
|
|
284
|
+
# Construct SQL using string concatenation to avoid f-string literal detection
|
|
285
|
+
select_parts = ["SELECT TOP ", top_k_str, " ", id_column]
|
|
286
|
+
if content_column:
|
|
287
|
+
select_parts.extend([", ", content_column])
|
|
288
|
+
|
|
289
|
+
# Construct TO_VECTOR call with ? placeholder but avoid embedding dimension parameterization
|
|
290
|
+
# Use FLOAT datatype to match how test data was created (see tests/fixtures/embedding_generator.py:247)
|
|
291
|
+
vector_func_parts = [
|
|
292
|
+
", VECTOR_COSINE(",
|
|
293
|
+
vector_column,
|
|
294
|
+
", TO_VECTOR(?, FLOAT, ",
|
|
295
|
+
embedding_dim_str,
|
|
296
|
+
")) AS score",
|
|
297
|
+
]
|
|
298
|
+
select_parts.extend(vector_func_parts)
|
|
299
|
+
select_clause = "".join(select_parts)
|
|
300
|
+
|
|
301
|
+
# Construct the WHERE clause
|
|
302
|
+
where_parts = ["WHERE ", vector_column, " IS NOT NULL"]
|
|
303
|
+
if additional_where:
|
|
304
|
+
where_parts.extend([" AND (", additional_where, ")"])
|
|
305
|
+
where_clause = "".join(where_parts)
|
|
306
|
+
|
|
307
|
+
# Construct the full SQL query using string concatenation
|
|
308
|
+
sql_parts = [
|
|
309
|
+
select_clause,
|
|
310
|
+
" FROM ",
|
|
311
|
+
table_name,
|
|
312
|
+
" ",
|
|
313
|
+
where_clause,
|
|
314
|
+
" ORDER BY score DESC",
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
return "".join(sql_parts)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def execute_vector_search_with_params(
|
|
321
|
+
cursor: Any, sql: str, vector_string: str, table_name: str = "RAG.SourceDocuments"
|
|
322
|
+
) -> List[Tuple]:
|
|
323
|
+
"""
|
|
324
|
+
@deprecated - Quarantined pending IRIS fix
|
|
325
|
+
See docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
326
|
+
|
|
327
|
+
Executes a vector search SQL query using parameters.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
cursor: A database cursor object
|
|
331
|
+
sql: The SQL query with ? placeholder
|
|
332
|
+
vector_string: The vector string to use as parameter
|
|
333
|
+
table_name: The table name for diagnostic queries (optional, defaults to RAG.SourceDocuments)
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
List[Tuple]: The query results
|
|
337
|
+
"""
|
|
338
|
+
results = []
|
|
339
|
+
try:
|
|
340
|
+
# Use the provided table name directly instead of parsing from SQL
|
|
341
|
+
logger.debug(f"Using table name: {table_name}")
|
|
342
|
+
|
|
343
|
+
count_sql = f"SELECT COUNT(*) FROM {table_name} WHERE embedding IS NOT NULL"
|
|
344
|
+
logger.debug(f"Executing count SQL: {count_sql}")
|
|
345
|
+
try:
|
|
346
|
+
cursor.execute(count_sql)
|
|
347
|
+
embedding_result = cursor.fetchone()
|
|
348
|
+
# Handle both real results and mock objects
|
|
349
|
+
if embedding_result:
|
|
350
|
+
try:
|
|
351
|
+
embedding_count = (
|
|
352
|
+
embedding_result[0]
|
|
353
|
+
if hasattr(embedding_result, "__getitem__")
|
|
354
|
+
else 0
|
|
355
|
+
)
|
|
356
|
+
except (TypeError, IndexError):
|
|
357
|
+
# Handle Mock objects or other non-subscriptable results
|
|
358
|
+
embedding_count = 0
|
|
359
|
+
else:
|
|
360
|
+
embedding_count = 0
|
|
361
|
+
logger.debug(
|
|
362
|
+
f"Table {table_name} has {embedding_count} rows with embeddings"
|
|
363
|
+
)
|
|
364
|
+
except Exception as count_error:
|
|
365
|
+
logger.error(f"Error executing count SQL: {count_error}")
|
|
366
|
+
logger.error(f"Count SQL was: {count_sql}")
|
|
367
|
+
# Skip count check and proceed with vector search
|
|
368
|
+
embedding_count = 0
|
|
369
|
+
|
|
370
|
+
# Also check total rows
|
|
371
|
+
total_sql = f"SELECT COUNT(*) FROM {table_name}"
|
|
372
|
+
logger.debug(f"Executing total SQL: {total_sql}")
|
|
373
|
+
try:
|
|
374
|
+
cursor.execute(total_sql)
|
|
375
|
+
total_result = cursor.fetchone()
|
|
376
|
+
# Handle both real results and mock objects
|
|
377
|
+
if total_result:
|
|
378
|
+
try:
|
|
379
|
+
total_count = (
|
|
380
|
+
total_result[0] if hasattr(total_result, "__getitem__") else 0
|
|
381
|
+
)
|
|
382
|
+
except (TypeError, IndexError):
|
|
383
|
+
# Handle Mock objects or other non-subscriptable results
|
|
384
|
+
total_count = 0
|
|
385
|
+
else:
|
|
386
|
+
total_count = 0
|
|
387
|
+
logger.debug(f"Table {table_name} has {total_count} total rows")
|
|
388
|
+
except Exception as total_error:
|
|
389
|
+
logger.error(f"Error executing total count SQL: {total_error}")
|
|
390
|
+
logger.error(f"Total SQL was: {total_sql}")
|
|
391
|
+
# Skip total count check and proceed with vector search
|
|
392
|
+
total_count = 0
|
|
393
|
+
|
|
394
|
+
logger.debug(f"Executing vector search SQL: {sql}")
|
|
395
|
+
logger.debug(f"Vector string parameter: {vector_string[:100]}...")
|
|
396
|
+
|
|
397
|
+
# Execute the SQL with parameter binding
|
|
398
|
+
cursor.execute(sql, [vector_string])
|
|
399
|
+
|
|
400
|
+
# Try to fetch results with better error handling
|
|
401
|
+
try:
|
|
402
|
+
fetched_rows = cursor.fetchall()
|
|
403
|
+
if fetched_rows:
|
|
404
|
+
results = fetched_rows
|
|
405
|
+
# Handle Mock objects that don't have len()
|
|
406
|
+
try:
|
|
407
|
+
result_count = len(results)
|
|
408
|
+
logger.debug(f"Found {result_count} results.")
|
|
409
|
+
except (TypeError, AttributeError):
|
|
410
|
+
# Handle Mock objects or other non-sequence types
|
|
411
|
+
logger.debug("Found results (count unavailable due to mock object)")
|
|
412
|
+
else:
|
|
413
|
+
logger.debug("No results returned from vector search")
|
|
414
|
+
except StopIteration as e:
|
|
415
|
+
logger.error(f"StopIteration error during fetchall(): {e}")
|
|
416
|
+
logger.error(
|
|
417
|
+
"This usually indicates the cursor is empty or in an invalid state"
|
|
418
|
+
)
|
|
419
|
+
# Return empty results instead of raising
|
|
420
|
+
results = []
|
|
421
|
+
except Exception as fetch_error:
|
|
422
|
+
logger.error(f"Error during fetchall(): {fetch_error}")
|
|
423
|
+
raise
|
|
424
|
+
except Exception as e:
|
|
425
|
+
logger.error(f"Error during vector search: {e}")
|
|
426
|
+
logger.error(f"SQL was: {sql}")
|
|
427
|
+
logger.error(f"Vector parameter was: {vector_string[:100]}...")
|
|
428
|
+
raise
|
|
429
|
+
return results
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
# =============================================================================
|
|
433
|
+
# SAFE VECTOR UTILITIES (PROVEN PATTERN)
|
|
434
|
+
# =============================================================================
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def build_safe_vector_dot_sql(
|
|
438
|
+
table: str,
|
|
439
|
+
vector_column: str,
|
|
440
|
+
vector_string: str,
|
|
441
|
+
vector_dimension: int,
|
|
442
|
+
id_column: str = "doc_id",
|
|
443
|
+
extra_columns: list[str] | None = None,
|
|
444
|
+
top_k: int = 5,
|
|
445
|
+
additional_where: str | None = None,
|
|
446
|
+
) -> str:
|
|
447
|
+
"""
|
|
448
|
+
Build safe vector search SQL with embedded vector string.
|
|
449
|
+
|
|
450
|
+
CRITICAL: TO_VECTOR() does NOT accept parameter markers (?, :param, etc.)
|
|
451
|
+
Vector string MUST be embedded directly in SQL with data type and dimension.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
table: Table name (e.g., "RAG.SourceDocuments")
|
|
455
|
+
vector_column: Vector column name (e.g., "embedding")
|
|
456
|
+
vector_string: Vector as bracketed comma-separated string (e.g., "[0.1,0.2,0.3]")
|
|
457
|
+
vector_dimension: Dimension of the vector (e.g., 384)
|
|
458
|
+
id_column: ID column name (default: "doc_id")
|
|
459
|
+
extra_columns: Additional columns to select (optional)
|
|
460
|
+
top_k: Number of results to return (default: 5)
|
|
461
|
+
additional_where: Additional WHERE conditions (optional)
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
str: Safe SQL query with embedded vector string
|
|
465
|
+
|
|
466
|
+
Example:
|
|
467
|
+
sql = build_safe_vector_dot_sql("RAG.SourceDocuments", "embedding", "[0.1,0.2,0.3]", 384, "doc_id", ["title"], 5)
|
|
468
|
+
cursor.execute(sql) # No parameters needed - vector embedded in SQL
|
|
469
|
+
"""
|
|
470
|
+
# Validate identifiers to prevent SQL injection
|
|
471
|
+
import re
|
|
472
|
+
|
|
473
|
+
# Validate table name (allow schema.table format)
|
|
474
|
+
if not re.match(r"^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)?$", table):
|
|
475
|
+
raise ValueError(f"Invalid table name: {table}")
|
|
476
|
+
|
|
477
|
+
# Validate column names
|
|
478
|
+
for col in [vector_column, id_column]:
|
|
479
|
+
if not re.match(r"^[a-zA-Z0-9_]+$", col):
|
|
480
|
+
raise ValueError(f"Invalid column name: {col}")
|
|
481
|
+
|
|
482
|
+
if extra_columns:
|
|
483
|
+
for col in extra_columns:
|
|
484
|
+
if not re.match(r"^[a-zA-Z0-9_]+$", col):
|
|
485
|
+
raise ValueError(f"Invalid extra column name: {col}")
|
|
486
|
+
|
|
487
|
+
# Validate top_k
|
|
488
|
+
if not isinstance(top_k, int) or top_k <= 0:
|
|
489
|
+
raise ValueError(f"Invalid top_k value: {top_k}")
|
|
490
|
+
|
|
491
|
+
# Validate vector_dimension
|
|
492
|
+
if not isinstance(vector_dimension, int) or vector_dimension <= 0:
|
|
493
|
+
raise ValueError(f"Invalid vector_dimension: {vector_dimension}")
|
|
494
|
+
|
|
495
|
+
# Validate vector string (security check)
|
|
496
|
+
if not validate_vector_string(vector_string):
|
|
497
|
+
raise ValueError(f"Invalid vector string: {vector_string}")
|
|
498
|
+
|
|
499
|
+
# Build SELECT clause
|
|
500
|
+
select_parts = [f"SELECT TOP {top_k} {id_column}"]
|
|
501
|
+
if extra_columns:
|
|
502
|
+
select_parts.extend([f", {col}" for col in extra_columns])
|
|
503
|
+
# IMPORTANT: Embed vector string directly with FLOAT type and dimension
|
|
504
|
+
# TO_VECTOR does NOT accept ? parameters
|
|
505
|
+
# Use FLOAT to match how test data was created (see tests/fixtures/embedding_generator.py:247)
|
|
506
|
+
select_parts.append(f", VECTOR_DOT_PRODUCT({vector_column}, TO_VECTOR('{vector_string}', FLOAT, {vector_dimension})) AS score")
|
|
507
|
+
|
|
508
|
+
# Build FROM clause
|
|
509
|
+
from_clause = f" FROM {table}"
|
|
510
|
+
|
|
511
|
+
# Build WHERE clause
|
|
512
|
+
where_parts = [f" WHERE {vector_column} IS NOT NULL"]
|
|
513
|
+
if additional_where:
|
|
514
|
+
where_parts.append(f" AND ({additional_where})")
|
|
515
|
+
|
|
516
|
+
# Build ORDER BY clause
|
|
517
|
+
order_clause = " ORDER BY score DESC"
|
|
518
|
+
|
|
519
|
+
# Combine all parts
|
|
520
|
+
sql = "".join(select_parts) + from_clause + "".join(where_parts) + order_clause
|
|
521
|
+
return sql
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def execute_safe_vector_search(
|
|
525
|
+
cursor, sql: str
|
|
526
|
+
) -> list[tuple]:
|
|
527
|
+
"""
|
|
528
|
+
Execute safe vector search with embedded vector string.
|
|
529
|
+
|
|
530
|
+
IMPORTANT: The vector string is already embedded in the SQL
|
|
531
|
+
(from build_safe_vector_dot_sql), so NO parameters are passed.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
cursor: Database cursor
|
|
535
|
+
sql: SQL query from build_safe_vector_dot_sql() with embedded vector
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
list[tuple]: Query results
|
|
539
|
+
|
|
540
|
+
Example:
|
|
541
|
+
sql = build_safe_vector_dot_sql("RAG.SourceDocuments", "embedding", "[0.1,0.2,0.3]")
|
|
542
|
+
results = execute_safe_vector_search(cursor, sql)
|
|
543
|
+
"""
|
|
544
|
+
# Execute with NO parameters - vector is embedded in SQL
|
|
545
|
+
cursor.execute(sql)
|
|
546
|
+
return cursor.fetchall()
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def execute_vector_search(cursor: Any, sql: str) -> List[Tuple]:
|
|
550
|
+
"""
|
|
551
|
+
@deprecated - Quarantined pending IRIS fix
|
|
552
|
+
See docs/reports/IRIS_VECTOR_SQL_PARAMETERIZATION_REPRO.md
|
|
553
|
+
|
|
554
|
+
Executes a vector search SQL query using the provided cursor.
|
|
555
|
+
Handles common errors and returns the results.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
cursor: A database cursor object
|
|
559
|
+
sql: The SQL query to execute
|
|
560
|
+
|
|
561
|
+
Returns:
|
|
562
|
+
List[Tuple]: The query results
|
|
563
|
+
|
|
564
|
+
Raises:
|
|
565
|
+
Exception: If the query execution fails
|
|
566
|
+
|
|
567
|
+
Example:
|
|
568
|
+
>>> cursor = connection.cursor()
|
|
569
|
+
>>> sql = format_vector_search_sql(...)
|
|
570
|
+
>>> results = execute_vector_search(cursor, sql)
|
|
571
|
+
"""
|
|
572
|
+
results = []
|
|
573
|
+
try:
|
|
574
|
+
logger.debug(f"Executing vector search SQL: {sql[:100]}...")
|
|
575
|
+
cursor.execute(sql) # No parameters passed as all are interpolated
|
|
576
|
+
fetched_rows = cursor.fetchall()
|
|
577
|
+
if fetched_rows:
|
|
578
|
+
results = fetched_rows
|
|
579
|
+
logger.debug(f"Found {len(results)} results.")
|
|
580
|
+
except Exception as e:
|
|
581
|
+
logger.error(f"Error during vector search: {e}")
|
|
582
|
+
# Re-raise the exception so the calling pipeline can handle it
|
|
583
|
+
raise
|
|
584
|
+
return results
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Biomedical RAG Pipeline Evaluation Framework
|
|
3
|
+
|
|
4
|
+
A comprehensive, statistically rigorous evaluation system for comparing
|
|
5
|
+
biomedical RAG pipelines with empirical evidence and actionable insights.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "1.0.0"
|
|
9
|
+
__author__ = "RAG Templates Team"
|
|
10
|
+
|
|
11
|
+
# Import warnings configuration
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
15
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
16
|
+
|
|
17
|
+
# Core components
|
|
18
|
+
try:
|
|
19
|
+
from .biomedical_question_generator import (
|
|
20
|
+
QuestionGenerationConfig,
|
|
21
|
+
create_biomedical_question_generator,
|
|
22
|
+
)
|
|
23
|
+
except ImportError as e:
|
|
24
|
+
print(f"Warning: Question generator not available - {e}")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from .ragas_metrics_framework import create_biomedical_ragas_framework
|
|
28
|
+
except ImportError as e:
|
|
29
|
+
print(f"Warning: RAGAS framework not available - {e}")
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from .statistical_evaluation_methodology import create_statistical_framework
|
|
33
|
+
except ImportError as e:
|
|
34
|
+
print(f"Warning: Statistical framework not available - {e}")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from .comparative_analysis_system import create_comparative_analysis_system
|
|
38
|
+
except ImportError as e:
|
|
39
|
+
print(f"Warning: Comparative analysis not available - {e}")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
from .pmc_data_pipeline import PMCProcessingConfig, create_pmc_data_pipeline
|
|
43
|
+
except ImportError as e:
|
|
44
|
+
print(f"Warning: PMC pipeline not available - {e}")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from .evaluation_orchestrator import EvaluationOrchestrator
|
|
48
|
+
except ImportError as e:
|
|
49
|
+
print(f"Warning: Evaluation orchestrator not available - {e}")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
from .empirical_reporting import create_empirical_reporting_framework
|
|
53
|
+
except ImportError as e:
|
|
54
|
+
print(f"Warning: Empirical reporting not available - {e}")
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"create_biomedical_question_generator",
|
|
58
|
+
"QuestionGenerationConfig",
|
|
59
|
+
"create_biomedical_ragas_framework",
|
|
60
|
+
"create_statistical_framework",
|
|
61
|
+
"create_comparative_analysis_system",
|
|
62
|
+
"create_pmc_data_pipeline",
|
|
63
|
+
"PMCProcessingConfig",
|
|
64
|
+
"EvaluationOrchestrator",
|
|
65
|
+
"create_empirical_reporting_framework",
|
|
66
|
+
]
|