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,289 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Database Schema Manager for RAG Templates
|
|
4
|
+
Provides centralized, config-driven table and column name resolution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class TableConfig:
|
|
19
|
+
"""Configuration for a database table."""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
alias: Optional[str] = None
|
|
23
|
+
description: str = ""
|
|
24
|
+
columns: Dict[str, str] = field(default_factory=dict)
|
|
25
|
+
indexes: List[Dict[str, Any]] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class SchemaConfig:
|
|
30
|
+
"""Complete database schema configuration."""
|
|
31
|
+
|
|
32
|
+
name: str
|
|
33
|
+
description: str = ""
|
|
34
|
+
tables: Dict[str, TableConfig] = field(default_factory=dict)
|
|
35
|
+
column_mappings: Dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
data_types: Dict[str, str] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DatabaseSchemaManager:
|
|
40
|
+
"""
|
|
41
|
+
Centralized manager for database schema configuration.
|
|
42
|
+
Eliminates hardcoded table/column names throughout the codebase.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
46
|
+
"""Initialize with schema configuration."""
|
|
47
|
+
if config_path is None:
|
|
48
|
+
# Default to config/database_schema.yaml in project root
|
|
49
|
+
project_root = Path(__file__).parent.parent
|
|
50
|
+
config_path = project_root / "config" / "database_schema.yaml"
|
|
51
|
+
|
|
52
|
+
self.config_path = Path(config_path)
|
|
53
|
+
self.schema_config = self._load_schema_config()
|
|
54
|
+
|
|
55
|
+
# Cache for performance
|
|
56
|
+
self._table_cache = {}
|
|
57
|
+
self._column_cache = {}
|
|
58
|
+
|
|
59
|
+
logger.info(f"✅ Database schema manager initialized from {config_path}")
|
|
60
|
+
|
|
61
|
+
def _load_schema_config(self) -> SchemaConfig:
|
|
62
|
+
"""Load schema configuration from YAML file."""
|
|
63
|
+
try:
|
|
64
|
+
with open(self.config_path, "r") as f:
|
|
65
|
+
config_data = yaml.safe_load(f)
|
|
66
|
+
|
|
67
|
+
# Parse schema info
|
|
68
|
+
schema_info = config_data.get("schema", {})
|
|
69
|
+
schema_name = schema_info.get("name", "RAG")
|
|
70
|
+
schema_desc = schema_info.get("description", "")
|
|
71
|
+
|
|
72
|
+
# Parse tables
|
|
73
|
+
tables = {}
|
|
74
|
+
tables_data = config_data.get("tables", {})
|
|
75
|
+
for table_key, table_data in tables_data.items():
|
|
76
|
+
tables[table_key] = TableConfig(
|
|
77
|
+
name=table_data.get("name", table_key),
|
|
78
|
+
alias=table_data.get("alias"),
|
|
79
|
+
description=table_data.get("description", ""),
|
|
80
|
+
columns=table_data.get("columns", {}),
|
|
81
|
+
indexes=table_data.get("indexes", []),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return SchemaConfig(
|
|
85
|
+
name=schema_name,
|
|
86
|
+
description=schema_desc,
|
|
87
|
+
tables=tables,
|
|
88
|
+
column_mappings=config_data.get("column_mappings", {}),
|
|
89
|
+
data_types=config_data.get("data_types", {}),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"Failed to load schema config from {self.config_path}: {e}")
|
|
94
|
+
raise
|
|
95
|
+
|
|
96
|
+
def get_schema_name(self) -> str:
|
|
97
|
+
"""Get the schema name (e.g., 'RAG')."""
|
|
98
|
+
return self.schema_config.name
|
|
99
|
+
|
|
100
|
+
def get_table_name(self, table_key: str, fully_qualified: bool = True) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Get the actual table name for a logical table key.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
table_key: Logical name (e.g., 'source_documents')
|
|
106
|
+
fully_qualified: Whether to include schema prefix (e.g., 'RAG.SourceDocuments')
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Actual table name
|
|
110
|
+
"""
|
|
111
|
+
if table_key in self._table_cache:
|
|
112
|
+
table_name = self._table_cache[table_key]
|
|
113
|
+
else:
|
|
114
|
+
if table_key not in self.schema_config.tables:
|
|
115
|
+
raise ValueError(f"Unknown table key: {table_key}")
|
|
116
|
+
|
|
117
|
+
table_config = self.schema_config.tables[table_key]
|
|
118
|
+
table_name = table_config.name
|
|
119
|
+
self._table_cache[table_key] = table_name
|
|
120
|
+
|
|
121
|
+
if fully_qualified:
|
|
122
|
+
return f"{self.schema_config.name}.{table_name}"
|
|
123
|
+
return table_name
|
|
124
|
+
|
|
125
|
+
def get_column_name(self, table_key: str, column_key: str) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Get the actual column name for a logical column key.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
table_key: Logical table name
|
|
131
|
+
column_key: Logical column name
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Actual column name
|
|
135
|
+
"""
|
|
136
|
+
cache_key = f"{table_key}.{column_key}"
|
|
137
|
+
if cache_key in self._column_cache:
|
|
138
|
+
return self._column_cache[cache_key]
|
|
139
|
+
|
|
140
|
+
if table_key not in self.schema_config.tables:
|
|
141
|
+
raise ValueError(f"Unknown table key: {table_key}")
|
|
142
|
+
|
|
143
|
+
table_config = self.schema_config.tables[table_key]
|
|
144
|
+
if column_key not in table_config.columns:
|
|
145
|
+
raise ValueError(
|
|
146
|
+
f"Unknown column key '{column_key}' for table '{table_key}'"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
column_name = table_config.columns[column_key]
|
|
150
|
+
self._column_cache[cache_key] = column_name
|
|
151
|
+
return column_name
|
|
152
|
+
|
|
153
|
+
def get_qualified_column(self, table_key: str, column_key: str) -> str:
|
|
154
|
+
"""Get fully qualified column name (table.column)."""
|
|
155
|
+
table_name = self.get_table_name(table_key, fully_qualified=False)
|
|
156
|
+
column_name = self.get_column_name(table_key, column_key)
|
|
157
|
+
return f"{table_name}.{column_name}"
|
|
158
|
+
|
|
159
|
+
def standardize_column_name(self, column_name: str) -> str:
|
|
160
|
+
"""
|
|
161
|
+
Standardize a column name using the mappings.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
column_name: Original column name
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Standardized column name
|
|
168
|
+
"""
|
|
169
|
+
mappings = self.schema_config.column_mappings
|
|
170
|
+
|
|
171
|
+
# Check each mapping category
|
|
172
|
+
for mapping_key, mapping_data in mappings.items():
|
|
173
|
+
if isinstance(mapping_data, dict) and "standard" in mapping_data:
|
|
174
|
+
variants = mapping_data.get("variants", [])
|
|
175
|
+
if column_name in variants:
|
|
176
|
+
return mapping_data["standard"]
|
|
177
|
+
|
|
178
|
+
# Return original if no mapping found
|
|
179
|
+
return column_name
|
|
180
|
+
|
|
181
|
+
def get_data_type(self, type_key: str) -> str:
|
|
182
|
+
"""Get data type definition."""
|
|
183
|
+
return self.schema_config.data_types.get(type_key, "VARCHAR(255)")
|
|
184
|
+
|
|
185
|
+
def build_create_table_sql(self, table_key: str) -> str:
|
|
186
|
+
"""Generate CREATE TABLE SQL for a table."""
|
|
187
|
+
if table_key not in self.schema_config.tables:
|
|
188
|
+
raise ValueError(f"Unknown table key: {table_key}")
|
|
189
|
+
|
|
190
|
+
table_config = self.schema_config.tables[table_key]
|
|
191
|
+
table_name = self.get_table_name(table_key, fully_qualified=True)
|
|
192
|
+
|
|
193
|
+
# Build column definitions
|
|
194
|
+
column_defs = []
|
|
195
|
+
for col_key, col_name in table_config.columns.items():
|
|
196
|
+
data_type = self.get_data_type(col_key)
|
|
197
|
+
column_defs.append(f" {col_name} {data_type}")
|
|
198
|
+
|
|
199
|
+
sql = f"CREATE TABLE IF NOT EXISTS {table_name} (\n"
|
|
200
|
+
sql += ",\n".join(column_defs)
|
|
201
|
+
sql += "\n);"
|
|
202
|
+
|
|
203
|
+
return sql
|
|
204
|
+
|
|
205
|
+
def get_all_tables(self) -> Dict[str, str]:
|
|
206
|
+
"""Get mapping of all table keys to actual names."""
|
|
207
|
+
return {
|
|
208
|
+
key: self.get_table_name(key, fully_qualified=True)
|
|
209
|
+
for key in self.schema_config.tables.keys()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
def validate_table_exists(self, table_key: str) -> bool:
|
|
213
|
+
"""Check if a table configuration exists."""
|
|
214
|
+
return table_key in self.schema_config.tables
|
|
215
|
+
|
|
216
|
+
def get_table_info(self, table_key: str) -> Dict[str, Any]:
|
|
217
|
+
"""Get complete information about a table."""
|
|
218
|
+
if table_key not in self.schema_config.tables:
|
|
219
|
+
raise ValueError(f"Unknown table key: {table_key}")
|
|
220
|
+
|
|
221
|
+
table_config = self.schema_config.tables[table_key]
|
|
222
|
+
return {
|
|
223
|
+
"key": table_key,
|
|
224
|
+
"name": table_config.name,
|
|
225
|
+
"alias": table_config.alias,
|
|
226
|
+
"description": table_config.description,
|
|
227
|
+
"fully_qualified_name": self.get_table_name(
|
|
228
|
+
table_key, fully_qualified=True
|
|
229
|
+
),
|
|
230
|
+
"columns": table_config.columns,
|
|
231
|
+
"indexes": table_config.indexes,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# Global schema manager instance
|
|
236
|
+
_schema_manager = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def get_schema_manager() -> DatabaseSchemaManager:
|
|
240
|
+
"""Get the global schema manager instance."""
|
|
241
|
+
global _schema_manager
|
|
242
|
+
if _schema_manager is None:
|
|
243
|
+
_schema_manager = DatabaseSchemaManager()
|
|
244
|
+
return _schema_manager
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# Convenience functions for common operations
|
|
248
|
+
def get_table(table_key: str, fully_qualified: bool = True) -> str:
|
|
249
|
+
"""Convenience function to get table name."""
|
|
250
|
+
return get_schema_manager().get_table_name(table_key, fully_qualified)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_column(table_key: str, column_key: str) -> str:
|
|
254
|
+
"""Convenience function to get column name."""
|
|
255
|
+
return get_schema_manager().get_column_name(table_key, column_key)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_qualified_column(table_key: str, column_key: str) -> str:
|
|
259
|
+
"""Convenience function to get qualified column name."""
|
|
260
|
+
return get_schema_manager().get_qualified_column(table_key, column_key)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# Example usage and testing
|
|
264
|
+
if __name__ == "__main__":
|
|
265
|
+
# Test the schema manager
|
|
266
|
+
logging.basicConfig(level=logging.INFO)
|
|
267
|
+
|
|
268
|
+
manager = DatabaseSchemaManager()
|
|
269
|
+
|
|
270
|
+
print("🔍 Testing Database Schema Manager")
|
|
271
|
+
print("=" * 50)
|
|
272
|
+
|
|
273
|
+
# Test table names
|
|
274
|
+
print("Table Names:")
|
|
275
|
+
for table_key in ["source_documents", "document_chunks", "document_entities"]:
|
|
276
|
+
name = manager.get_table_name(table_key)
|
|
277
|
+
print(f" {table_key} → {name}")
|
|
278
|
+
|
|
279
|
+
print("\nColumn Names:")
|
|
280
|
+
for table_key in ["source_documents", "document_chunks"]:
|
|
281
|
+
for col_key in ["id", "content"]:
|
|
282
|
+
try:
|
|
283
|
+
col_name = manager.get_column_name(table_key, col_key)
|
|
284
|
+
print(f" {table_key}.{col_key} → {col_name}")
|
|
285
|
+
except ValueError as e:
|
|
286
|
+
print(f" {table_key}.{col_key} → ERROR: {e}")
|
|
287
|
+
|
|
288
|
+
print("\nCreate Table SQL:")
|
|
289
|
+
print(manager.build_create_table_sql("source_documents"))
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Complete database initialization script with all tables and indexes.
|
|
4
|
+
This script creates a fresh RAG database with all optimizations included.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Add project root to path
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
13
|
+
|
|
14
|
+
from .iris_connection_manager import get_iris_connection
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(level=logging.INFO)
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def initialize_complete_rag_database(schema: str = "RAG"):
|
|
21
|
+
"""
|
|
22
|
+
Initialize complete RAG database with all tables and indexes.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
schema: Database schema name (default: "RAG")
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
conn = get_iris_connection()
|
|
29
|
+
cursor = conn.cursor()
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
logger.info(f"🚀 Initializing complete RAG database in schema: {schema}")
|
|
33
|
+
|
|
34
|
+
# Read and execute the complete SQL schema
|
|
35
|
+
sql_file_path = Path(__file__).parent / "db_init_complete.sql"
|
|
36
|
+
|
|
37
|
+
if sql_file_path.exists():
|
|
38
|
+
with open(sql_file_path, "r") as f:
|
|
39
|
+
sql_content = f.read()
|
|
40
|
+
|
|
41
|
+
# Replace RAG with the specified schema
|
|
42
|
+
sql_content = sql_content.replace("RAG.", f"{schema}.")
|
|
43
|
+
|
|
44
|
+
# Split by semicolons and execute each statement
|
|
45
|
+
statements = [
|
|
46
|
+
stmt.strip() for stmt in sql_content.split(";") if stmt.strip()
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
# Separate table creation from index creation
|
|
50
|
+
table_statements = []
|
|
51
|
+
index_statements = []
|
|
52
|
+
|
|
53
|
+
for statement in statements:
|
|
54
|
+
if statement and not statement.startswith("--"):
|
|
55
|
+
if statement.upper().startswith("CREATE INDEX"):
|
|
56
|
+
index_statements.append(statement)
|
|
57
|
+
else:
|
|
58
|
+
table_statements.append(statement)
|
|
59
|
+
|
|
60
|
+
# Execute table creation statements first
|
|
61
|
+
for i, statement in enumerate(table_statements):
|
|
62
|
+
try:
|
|
63
|
+
cursor.execute(statement)
|
|
64
|
+
logger.debug(
|
|
65
|
+
f"✅ Executed table statement {i+1}/{len(table_statements)}"
|
|
66
|
+
)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
if (
|
|
69
|
+
"already exists" in str(e).lower()
|
|
70
|
+
or "duplicate" in str(e).lower()
|
|
71
|
+
):
|
|
72
|
+
logger.debug(f"⚠️ Table statement {i+1} - object already exists")
|
|
73
|
+
else:
|
|
74
|
+
logger.warning(f"⚠️ Table statement {i+1} failed: {e}")
|
|
75
|
+
|
|
76
|
+
# Use the index utility for index creation
|
|
77
|
+
if index_statements:
|
|
78
|
+
logger.info("Creating indexes with proper error handling...")
|
|
79
|
+
failed_indexes = []
|
|
80
|
+
for statement in index_statements:
|
|
81
|
+
try:
|
|
82
|
+
# Replace "CREATE INDEX IF NOT EXISTS" with "CREATE INDEX"
|
|
83
|
+
statement = statement.replace("IF NOT EXISTS", "").replace(
|
|
84
|
+
"if not exists", ""
|
|
85
|
+
)
|
|
86
|
+
cursor.execute(statement)
|
|
87
|
+
logger.debug(f"✅ Created index: {statement[:50]}...")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
error_str = str(e).lower()
|
|
90
|
+
if any(
|
|
91
|
+
indicator in error_str
|
|
92
|
+
for indicator in [
|
|
93
|
+
"already exists",
|
|
94
|
+
"duplicate",
|
|
95
|
+
"index exists",
|
|
96
|
+
"name already used",
|
|
97
|
+
]
|
|
98
|
+
):
|
|
99
|
+
logger.debug(
|
|
100
|
+
f"⚠️ Index already exists (ignored): {statement[:50]}..."
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
logger.warning(
|
|
104
|
+
f"⚠️ Index creation failed: {statement[:50]}... Error: {e}"
|
|
105
|
+
)
|
|
106
|
+
failed_indexes.append(statement)
|
|
107
|
+
|
|
108
|
+
if failed_indexes:
|
|
109
|
+
logger.warning(f"⚠️ {len(failed_indexes)} indexes failed to create")
|
|
110
|
+
else:
|
|
111
|
+
logger.info("✅ All indexes created successfully")
|
|
112
|
+
|
|
113
|
+
logger.info(f"✅ Schema initialization completed for {schema}")
|
|
114
|
+
else:
|
|
115
|
+
logger.error(f"❌ SQL file not found: {sql_file_path}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
# Verify tables were created
|
|
119
|
+
logger.info("🔍 Verifying table creation...")
|
|
120
|
+
|
|
121
|
+
expected_tables = [
|
|
122
|
+
"SourceDocuments",
|
|
123
|
+
"DocumentChunks",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
for table in expected_tables:
|
|
127
|
+
try:
|
|
128
|
+
cursor.execute(f"SELECT COUNT(*) FROM {schema}.{table}")
|
|
129
|
+
count = cursor.fetchone()[0]
|
|
130
|
+
logger.info(f"✅ {schema}.{table}: {count:,} rows")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.warning(f"❌ {schema}.{table}: {e}")
|
|
133
|
+
|
|
134
|
+
logger.info("🎉 Complete RAG database initialization successful!")
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logger.error(f"❌ Error during database initialization: {e}")
|
|
139
|
+
return False
|
|
140
|
+
finally:
|
|
141
|
+
cursor.close()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def create_schema_if_not_exists(schema: str = "RAG"):
|
|
145
|
+
"""Create schema if it doesn't exist"""
|
|
146
|
+
conn = get_iris_connection()
|
|
147
|
+
cursor = conn.cursor()
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
cursor.execute(f"CREATE SCHEMA IF NOT EXISTS {schema}")
|
|
151
|
+
logger.info(f"✅ Schema {schema} ready")
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.warning(f"Schema creation: {e}")
|
|
154
|
+
finally:
|
|
155
|
+
cursor.close()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
import argparse
|
|
160
|
+
|
|
161
|
+
parser = argparse.ArgumentParser(description="Initialize complete RAG database")
|
|
162
|
+
parser.add_argument(
|
|
163
|
+
"--schema", default="RAG", help="Database schema name (default: RAG)"
|
|
164
|
+
)
|
|
165
|
+
args = parser.parse_args()
|
|
166
|
+
|
|
167
|
+
# Create schema first
|
|
168
|
+
create_schema_if_not_exists(args.schema)
|
|
169
|
+
|
|
170
|
+
# Initialize database
|
|
171
|
+
success = initialize_complete_rag_database(args.schema)
|
|
172
|
+
|
|
173
|
+
if success:
|
|
174
|
+
print(
|
|
175
|
+
f"🎉 Database initialization completed successfully for schema: {args.schema}"
|
|
176
|
+
)
|
|
177
|
+
print("📋 All tables and indexes are ready for:")
|
|
178
|
+
print(" - BasicRAG, ReRanking, CRAG")
|
|
179
|
+
else:
|
|
180
|
+
print("❌ Database initialization failed")
|
|
181
|
+
sys.exit(1)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def insert_vector(
|
|
8
|
+
cursor: Any,
|
|
9
|
+
table_name: str,
|
|
10
|
+
vector_column_name: str,
|
|
11
|
+
vector_data: List[float],
|
|
12
|
+
target_dimension: int,
|
|
13
|
+
key_columns: Dict[str, Any],
|
|
14
|
+
additional_data: Optional[Dict[str, Any]] = None,
|
|
15
|
+
) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Inserts a record with a vector embedding into a specified table.
|
|
18
|
+
The vector is truncated to the target_dimension and inserted using TO_VECTOR(?).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
cursor: Database cursor.
|
|
22
|
+
table_name: Name of the table to insert into (e.g., "RAG.DocumentTokenEmbeddings").
|
|
23
|
+
vector_column_name: Name of the column that stores the vector.
|
|
24
|
+
vector_data: The raw embedding vector (list of floats).
|
|
25
|
+
target_dimension: The dimension to truncate/pad the vector to.
|
|
26
|
+
key_columns: Dictionary of primary key or identifying column names and their values.
|
|
27
|
+
(e.g., {"doc_id": "id1", "token_index": 0})
|
|
28
|
+
additional_data: Optional dictionary of other column names and their values.
|
|
29
|
+
(e.g., {"token_text": "example"})
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
True if insertion was successful, False otherwise.
|
|
33
|
+
"""
|
|
34
|
+
# Validate cursor handle
|
|
35
|
+
if cursor is None:
|
|
36
|
+
logger.error(
|
|
37
|
+
f"DB Vector Util: Cannot insert vector into table '{table_name}': cursor is NULL"
|
|
38
|
+
)
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
if not isinstance(vector_data, list) or not all(
|
|
42
|
+
isinstance(x, (float, int)) for x in vector_data
|
|
43
|
+
):
|
|
44
|
+
logger.error(
|
|
45
|
+
f"DB Vector Util: Invalid vector_data format for table '{table_name}'. "
|
|
46
|
+
f"Expected list of floats/ints. Got type: {type(vector_data)}. Skipping insertion."
|
|
47
|
+
)
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
# Truncate or pad the vector to the target dimension
|
|
51
|
+
processed_vector = vector_data[:target_dimension]
|
|
52
|
+
if len(processed_vector) < target_dimension:
|
|
53
|
+
logger.warning(
|
|
54
|
+
f"DB Vector Util: Original vector length ({len(vector_data)}) for table '{table_name}', column '{vector_column_name}' "
|
|
55
|
+
f"is less than target dimension ({target_dimension}). Padding with zeros."
|
|
56
|
+
)
|
|
57
|
+
processed_vector.extend([0.0] * (target_dimension - len(processed_vector)))
|
|
58
|
+
|
|
59
|
+
# Format as bracketed comma-separated string for IRIS TO_VECTOR() function
|
|
60
|
+
embedding_str = "[" + ",".join(map(str, processed_vector)) + "]"
|
|
61
|
+
|
|
62
|
+
all_columns_dict = {}
|
|
63
|
+
all_columns_dict.update(key_columns)
|
|
64
|
+
if additional_data:
|
|
65
|
+
all_columns_dict.update(additional_data)
|
|
66
|
+
|
|
67
|
+
# Separate vector column from other data for SQL construction
|
|
68
|
+
other_column_names = [col for col in all_columns_dict.keys()]
|
|
69
|
+
other_column_values = [all_columns_dict[col] for col in other_column_names]
|
|
70
|
+
|
|
71
|
+
column_names_sql = ", ".join(other_column_names + [vector_column_name])
|
|
72
|
+
|
|
73
|
+
# IMPORTANT: TO_VECTOR() does NOT accept parameter markers (?, :param, etc.)
|
|
74
|
+
# Must embed the vector string directly in SQL
|
|
75
|
+
# Use FLOAT to match how test data was created (see tests/fixtures/embedding_generator.py:247)
|
|
76
|
+
placeholders_list = ["?" for _ in other_column_names] + [
|
|
77
|
+
f"TO_VECTOR('{embedding_str}', FLOAT, {target_dimension})"
|
|
78
|
+
]
|
|
79
|
+
placeholders_sql = ", ".join(placeholders_list)
|
|
80
|
+
|
|
81
|
+
# Use MERGE for upsert functionality to handle duplicates
|
|
82
|
+
# Build MERGE statement for IRIS
|
|
83
|
+
key_conditions = " AND ".join(
|
|
84
|
+
[f"target.{col} = source.{col}" for col in key_columns.keys()]
|
|
85
|
+
)
|
|
86
|
+
update_assignments = ", ".join(
|
|
87
|
+
[
|
|
88
|
+
f"{col} = source.{col}"
|
|
89
|
+
for col in other_column_names
|
|
90
|
+
if col not in key_columns
|
|
91
|
+
]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Separate approach: try INSERT first, if it fails due to constraint, try UPDATE
|
|
95
|
+
sql_query = (
|
|
96
|
+
f"INSERT INTO {table_name} ({column_names_sql}) VALUES ({placeholders_sql})"
|
|
97
|
+
)
|
|
98
|
+
# Don't pass embedding_str as a parameter - it's embedded in SQL above
|
|
99
|
+
params = other_column_values
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
logger.debug(f"DB Vector Util: Executing INSERT: {sql_query}")
|
|
103
|
+
logger.debug(f"DB Vector Util: Parameters: {params}")
|
|
104
|
+
logger.debug(
|
|
105
|
+
f"DB Vector Util: Embedding string length: {len(embedding_str)} chars"
|
|
106
|
+
)
|
|
107
|
+
logger.debug(f"DB Vector Util: Vector dimension: {target_dimension}")
|
|
108
|
+
cursor.execute(sql_query, params)
|
|
109
|
+
return True
|
|
110
|
+
except Exception as e:
|
|
111
|
+
# Check for connection handle issues
|
|
112
|
+
error_str = str(e).lower()
|
|
113
|
+
if "_handle is null" in error_str or "handle is null" in error_str:
|
|
114
|
+
logger.error(
|
|
115
|
+
f"DB Vector Util: Database connection handle is NULL during vector insertion: {e}"
|
|
116
|
+
)
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
# Check if it's a unique constraint violation
|
|
120
|
+
if "UNIQUE" in str(e) or "constraint failed" in str(e):
|
|
121
|
+
logger.debug(
|
|
122
|
+
f"DB Vector Util: INSERT failed due to duplicate key, attempting UPDATE..."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Build UPDATE statement
|
|
126
|
+
set_clauses = []
|
|
127
|
+
update_params = []
|
|
128
|
+
|
|
129
|
+
# Add non-key columns to SET clause
|
|
130
|
+
for col in other_column_names:
|
|
131
|
+
if col not in key_columns:
|
|
132
|
+
set_clauses.append(f"{col} = ?")
|
|
133
|
+
update_params.append(all_columns_dict[col])
|
|
134
|
+
|
|
135
|
+
# Add vector column to SET clause - TO_VECTOR doesn't accept parameters
|
|
136
|
+
# Use FLOAT to match how test data was created (see tests/fixtures/embedding_generator.py:247)
|
|
137
|
+
set_clauses.append(
|
|
138
|
+
f"{vector_column_name} = TO_VECTOR('{embedding_str}', FLOAT, {target_dimension})"
|
|
139
|
+
)
|
|
140
|
+
# Don't append embedding_str to params - it's embedded in SQL above
|
|
141
|
+
|
|
142
|
+
# Add key columns to WHERE clause
|
|
143
|
+
where_clauses = []
|
|
144
|
+
for col, val in key_columns.items():
|
|
145
|
+
where_clauses.append(f"{col} = ?")
|
|
146
|
+
update_params.append(val)
|
|
147
|
+
|
|
148
|
+
if set_clauses and where_clauses:
|
|
149
|
+
update_sql = f"UPDATE {table_name} SET {', '.join(set_clauses)} WHERE {' AND '.join(where_clauses)}"
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
logger.debug(f"DB Vector Util: Executing UPDATE: {update_sql}")
|
|
153
|
+
cursor.execute(update_sql, update_params)
|
|
154
|
+
return True
|
|
155
|
+
except Exception as update_error:
|
|
156
|
+
# Check for connection handle issues in UPDATE
|
|
157
|
+
update_error_str = str(update_error).lower()
|
|
158
|
+
if (
|
|
159
|
+
"_handle is null" in update_error_str
|
|
160
|
+
or "handle is null" in update_error_str
|
|
161
|
+
):
|
|
162
|
+
logger.error(
|
|
163
|
+
f"DB Vector Util: Database connection handle is NULL during UPDATE: {update_error}"
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
logger.error(
|
|
167
|
+
f"DB Vector Util: UPDATE also failed: {update_error}"
|
|
168
|
+
)
|
|
169
|
+
return False
|
|
170
|
+
else:
|
|
171
|
+
logger.error(f"DB Vector Util: Could not build UPDATE statement")
|
|
172
|
+
return False
|
|
173
|
+
else:
|
|
174
|
+
logger.error(
|
|
175
|
+
f"DB Vector Util: Error inserting vector into table '{table_name}', column '{vector_column_name}': {e}"
|
|
176
|
+
)
|
|
177
|
+
logger.error(f"DB Vector Util: Key columns: {key_columns}")
|
|
178
|
+
logger.error(
|
|
179
|
+
f"DB Vector Util: Failing embedding string (first 100 chars): {embedding_str[:100] if 'embedding_str' in locals() else 'NOT_SET'}"
|
|
180
|
+
)
|
|
181
|
+
return False
|