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,166 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ IRIS Stream Reader Utility
4
+ Handles reading IRIS stream objects properly from JDBC connections.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def read_iris_stream(stream_obj: Any) -> str:
14
+ """
15
+ Read content from an IRIS stream object returned by JDBC.
16
+
17
+ Args:
18
+ stream_obj: Stream object from IRIS JDBC connection
19
+
20
+ Returns:
21
+ String content of the stream
22
+ """
23
+ if stream_obj is None:
24
+ return ""
25
+
26
+ try:
27
+ # Check if it's already a string
28
+ if isinstance(stream_obj, str):
29
+ return stream_obj
30
+
31
+ # Handle different stream types from DBAPI vs JDBC
32
+
33
+ # JDBC streams (Java objects) - use readAllBytes()
34
+ if (
35
+ hasattr(stream_obj, "readAllBytes")
36
+ and str(type(stream_obj)).find("java") != -1
37
+ ):
38
+ # Use readAllBytes for IRIS JDBC streams - this gets the actual content
39
+ try:
40
+ content_bytes = stream_obj.readAllBytes()
41
+ if hasattr(content_bytes, "__iter__"):
42
+ return bytes(content_bytes).decode("utf-8", errors="ignore")
43
+ else:
44
+ return str(content_bytes)
45
+ except Exception as e:
46
+ logger.warning(f"Failed to read JDBC stream with readAllBytes: {e}")
47
+
48
+ # DBAPI streams (Python objects) - use read()
49
+ elif hasattr(stream_obj, "read") and str(type(stream_obj)).find("java") == -1:
50
+ # For DBAPI streams, read() should work normally
51
+ try:
52
+ content_bytes = stream_obj.read()
53
+ if isinstance(content_bytes, bytes):
54
+ return content_bytes.decode("utf-8", errors="ignore")
55
+ elif isinstance(content_bytes, str):
56
+ return content_bytes
57
+ else:
58
+ return str(content_bytes)
59
+ except Exception as e:
60
+ logger.warning(f"Failed to read DBAPI stream: {e}")
61
+
62
+ # JDBC streams fallback - read() often returns length not content for JDBC
63
+ elif hasattr(stream_obj, "read"):
64
+ try:
65
+ content_bytes = stream_obj.read()
66
+ if isinstance(content_bytes, bytes):
67
+ return content_bytes.decode("utf-8", errors="ignore")
68
+ elif isinstance(content_bytes, str):
69
+ return content_bytes
70
+ else:
71
+ # For JDBC, this is likely a length, not content
72
+ if str(type(stream_obj)).find("java") != -1:
73
+ logger.debug(
74
+ f"JDBC stream.read() returned length ({content_bytes}), not content"
75
+ )
76
+ return ""
77
+ else:
78
+ return str(content_bytes)
79
+ except Exception as e:
80
+ logger.warning(f"Failed to read stream as fallback: {e}")
81
+
82
+ # Handle Java String objects
83
+ if hasattr(stream_obj, "toString"):
84
+ return str(stream_obj.toString())
85
+
86
+ # Handle numeric stream lengths (IRIS sometimes returns length instead of content)
87
+ if hasattr(stream_obj, "__int__") or str(type(stream_obj)).find("JInt") != -1:
88
+ logger.warning(
89
+ f"Stream object appears to be length ({stream_obj}), not content"
90
+ )
91
+ return ""
92
+
93
+ # Last resort - convert to string
94
+ return str(stream_obj)
95
+
96
+ except Exception as e:
97
+ logger.error(f"Failed to read IRIS stream: {e}")
98
+ return ""
99
+
100
+
101
+ def get_document_content_properly(cursor, doc_id: str) -> tuple[str, str, str]:
102
+ """
103
+ Get document content with proper stream handling.
104
+
105
+ Returns:
106
+ Tuple of (doc_id, title, content)
107
+ """
108
+ try:
109
+ cursor.execute(
110
+ """
111
+ SELECT doc_id, title, text_content
112
+ FROM RAG.SourceDocuments
113
+ WHERE doc_id = ?
114
+ """,
115
+ (doc_id,),
116
+ )
117
+
118
+ result = cursor.fetchone()
119
+ if not result:
120
+ return doc_id, "", ""
121
+
122
+ fetched_doc_id, title_stream, content_stream = result
123
+
124
+ # Read streams properly
125
+ title = read_iris_stream(title_stream)
126
+ content = read_iris_stream(content_stream)
127
+
128
+ return fetched_doc_id, title, content
129
+
130
+ except Exception as e:
131
+ logger.error(f"Failed to get document content for {doc_id}: {e}")
132
+ return doc_id, "", ""
133
+
134
+
135
+ def test_stream_reading():
136
+ """Test stream reading functionality."""
137
+ from iris_connector import get_iris_connection
138
+
139
+ conn = get_iris_connection()
140
+ cursor = conn.cursor()
141
+
142
+ try:
143
+ # Get a sample document
144
+ cursor.execute("SELECT TOP 1 doc_id FROM RAG.SourceDocuments")
145
+ result = cursor.fetchone()
146
+
147
+ if result:
148
+ doc_id = result[0]
149
+ doc_id, title, content = get_document_content_properly(cursor, doc_id)
150
+
151
+ print(f"Document ID: {doc_id}")
152
+ print(f"Title: {title[:100]}...")
153
+ print(f"Content: {content[:200]}...")
154
+ print(f"Content length: {len(content)}")
155
+
156
+ return len(content) > 0
157
+ else:
158
+ print("No documents found")
159
+ return False
160
+
161
+ finally:
162
+ cursor.close()
163
+
164
+
165
+ if __name__ == "__main__":
166
+ test_stream_reading()
@@ -0,0 +1,308 @@
1
+ """
2
+ LLM Cache Configuration Module
3
+
4
+ This module provides configuration management for the LLM caching layer,
5
+ supporting both YAML file configuration and environment variable overrides.
6
+ """
7
+
8
+ import os
9
+ import yaml
10
+ import logging
11
+ from dataclasses import dataclass
12
+ from typing import Dict, Any, Optional
13
+ from pathlib import Path
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class CacheConfig:
20
+ """Configuration model for LLM caching."""
21
+
22
+ # Core settings
23
+ enabled: bool = True
24
+ backend: str = "iris" # memory, iris, redis
25
+ ttl_seconds: int = 3600
26
+ normalize_prompts: bool = False
27
+ max_cache_size: int = 1000
28
+
29
+ # IRIS-specific settings
30
+ table_name: str = "llm_cache"
31
+ iris_schema: str = "RAG"
32
+ connection_timeout: int = 30
33
+ cleanup_batch_size: int = 1000
34
+ auto_cleanup: bool = True
35
+ cleanup_interval: int = 86400
36
+
37
+ # Key generation settings
38
+ include_temperature: bool = True
39
+ include_max_tokens: bool = True
40
+ include_model_name: bool = True
41
+ hash_algorithm: str = "sha256"
42
+ normalize_whitespace: bool = True
43
+ normalize_case: bool = False
44
+
45
+ # Monitoring settings
46
+ monitoring_enabled: bool = True
47
+ log_operations: bool = False
48
+ track_stats: bool = True
49
+ metrics_interval: int = 300
50
+
51
+ # Error handling settings
52
+ graceful_fallback: bool = True
53
+ max_retries: int = 3
54
+ retry_delay: int = 1
55
+ operation_timeout: int = 10
56
+
57
+ @classmethod
58
+ def from_env(cls) -> "CacheConfig":
59
+ """Create configuration from environment variables."""
60
+ config = cls()
61
+
62
+ # Override with environment variables if present
63
+ if "LLM_CACHE_ENABLED" in os.environ:
64
+ config.enabled = os.environ["LLM_CACHE_ENABLED"].lower() in (
65
+ "true",
66
+ "1",
67
+ "yes",
68
+ )
69
+
70
+ if "LLM_CACHE_BACKEND" in os.environ:
71
+ config.backend = os.environ["LLM_CACHE_BACKEND"]
72
+
73
+ if "LLM_CACHE_TTL" in os.environ:
74
+ config.ttl_seconds = int(os.environ["LLM_CACHE_TTL"])
75
+
76
+ if "LLM_CACHE_TABLE" in os.environ:
77
+ config.table_name = os.environ["LLM_CACHE_TABLE"]
78
+
79
+ if "LLM_CACHE_NORMALIZE_PROMPTS" in os.environ:
80
+ config.normalize_prompts = os.environ[
81
+ "LLM_CACHE_NORMALIZE_PROMPTS"
82
+ ].lower() in ("true", "1", "yes")
83
+
84
+ if "LLM_CACHE_MAX_SIZE" in os.environ:
85
+ config.max_cache_size = int(os.environ["LLM_CACHE_MAX_SIZE"])
86
+
87
+ if "LLM_CACHE_IRIS_SCHEMA" in os.environ:
88
+ config.iris_schema = os.environ["LLM_CACHE_IRIS_SCHEMA"]
89
+
90
+ logger.info(
91
+ f"Cache configuration loaded from environment: backend={config.backend}, enabled={config.enabled}"
92
+ )
93
+ return config
94
+
95
+ @classmethod
96
+ def from_yaml(cls, config_path: str = "config/cache_config.yaml") -> "CacheConfig":
97
+ """Create configuration from YAML file with environment variable overrides."""
98
+ config = cls()
99
+
100
+ # Load from YAML file if it exists
101
+ yaml_path = Path(config_path)
102
+ if yaml_path.exists():
103
+ try:
104
+ with open(yaml_path, "r") as f:
105
+ yaml_data = yaml.safe_load(f)
106
+
107
+ if "llm_cache" in yaml_data:
108
+ cache_data = yaml_data["llm_cache"]
109
+
110
+ # Core settings
111
+ config.enabled = cache_data.get("enabled", config.enabled)
112
+ config.backend = cache_data.get("backend", config.backend)
113
+ config.ttl_seconds = cache_data.get(
114
+ "ttl_seconds", config.ttl_seconds
115
+ )
116
+ config.normalize_prompts = cache_data.get(
117
+ "normalize_prompts", config.normalize_prompts
118
+ )
119
+ config.max_cache_size = cache_data.get(
120
+ "max_cache_size", config.max_cache_size
121
+ )
122
+
123
+ # IRIS settings
124
+ if "iris" in cache_data:
125
+ iris_data = cache_data["iris"]
126
+ config.table_name = iris_data.get(
127
+ "table_name", config.table_name
128
+ )
129
+ config.iris_schema = iris_data.get("schema", config.iris_schema)
130
+ config.connection_timeout = iris_data.get(
131
+ "connection_timeout", config.connection_timeout
132
+ )
133
+ config.cleanup_batch_size = iris_data.get(
134
+ "cleanup_batch_size", config.cleanup_batch_size
135
+ )
136
+ config.auto_cleanup = iris_data.get(
137
+ "auto_cleanup", config.auto_cleanup
138
+ )
139
+ config.cleanup_interval = iris_data.get(
140
+ "cleanup_interval", config.cleanup_interval
141
+ )
142
+
143
+ # Key generation settings
144
+ if "key_generation" in cache_data:
145
+ key_data = cache_data["key_generation"]
146
+ config.include_temperature = key_data.get(
147
+ "include_temperature", config.include_temperature
148
+ )
149
+ config.include_max_tokens = key_data.get(
150
+ "include_max_tokens", config.include_max_tokens
151
+ )
152
+ config.include_model_name = key_data.get(
153
+ "include_model_name", config.include_model_name
154
+ )
155
+ config.hash_algorithm = key_data.get(
156
+ "hash_algorithm", config.hash_algorithm
157
+ )
158
+ config.normalize_whitespace = key_data.get(
159
+ "normalize_whitespace", config.normalize_whitespace
160
+ )
161
+ config.normalize_case = key_data.get(
162
+ "normalize_case", config.normalize_case
163
+ )
164
+
165
+ # Monitoring settings
166
+ if "monitoring" in cache_data:
167
+ monitor_data = cache_data["monitoring"]
168
+ config.monitoring_enabled = monitor_data.get(
169
+ "enabled", config.monitoring_enabled
170
+ )
171
+ config.log_operations = monitor_data.get(
172
+ "log_operations", config.log_operations
173
+ )
174
+ config.track_stats = monitor_data.get(
175
+ "track_stats", config.track_stats
176
+ )
177
+ config.metrics_interval = monitor_data.get(
178
+ "metrics_interval", config.metrics_interval
179
+ )
180
+
181
+ # Error handling settings
182
+ if "error_handling" in cache_data:
183
+ error_data = cache_data["error_handling"]
184
+ config.graceful_fallback = error_data.get(
185
+ "graceful_fallback", config.graceful_fallback
186
+ )
187
+ config.max_retries = error_data.get(
188
+ "max_retries", config.max_retries
189
+ )
190
+ config.retry_delay = error_data.get(
191
+ "retry_delay", config.retry_delay
192
+ )
193
+ config.operation_timeout = error_data.get(
194
+ "operation_timeout", config.operation_timeout
195
+ )
196
+
197
+ logger.info(f"Cache configuration loaded from YAML: {config_path}")
198
+
199
+ except Exception as e:
200
+ logger.warning(
201
+ f"Failed to load cache config from {config_path}: {e}. Using defaults."
202
+ )
203
+ else:
204
+ logger.info(
205
+ f"Cache config file not found at {config_path}. Using defaults."
206
+ )
207
+
208
+ # Apply environment variable overrides
209
+ config = cls._apply_env_overrides(config)
210
+
211
+ return config
212
+
213
+ @classmethod
214
+ def _apply_env_overrides(cls, config: "CacheConfig") -> "CacheConfig":
215
+ """Apply environment variable overrides to configuration."""
216
+ # Override with environment variables if present
217
+ if "LLM_CACHE_ENABLED" in os.environ:
218
+ config.enabled = os.environ["LLM_CACHE_ENABLED"].lower() in (
219
+ "true",
220
+ "1",
221
+ "yes",
222
+ )
223
+
224
+ if "LLM_CACHE_BACKEND" in os.environ:
225
+ config.backend = os.environ["LLM_CACHE_BACKEND"]
226
+
227
+ if "LLM_CACHE_TTL" in os.environ:
228
+ config.ttl_seconds = int(os.environ["LLM_CACHE_TTL"])
229
+
230
+ if "LLM_CACHE_TABLE" in os.environ:
231
+ config.table_name = os.environ["LLM_CACHE_TABLE"]
232
+
233
+ if "LLM_CACHE_NORMALIZE_PROMPTS" in os.environ:
234
+ config.normalize_prompts = os.environ[
235
+ "LLM_CACHE_NORMALIZE_PROMPTS"
236
+ ].lower() in ("true", "1", "yes")
237
+
238
+ if "LLM_CACHE_MAX_SIZE" in os.environ:
239
+ config.max_cache_size = int(os.environ["LLM_CACHE_MAX_SIZE"])
240
+
241
+ if "LLM_CACHE_REDIS_URL" in os.environ:
242
+ config.redis_url = os.environ["LLM_CACHE_REDIS_URL"]
243
+
244
+ if "LLM_CACHE_IRIS_SCHEMA" in os.environ:
245
+ config.iris_schema = os.environ["LLM_CACHE_IRIS_SCHEMA"]
246
+
247
+ return config
248
+
249
+ def validate(self) -> bool:
250
+ """Validate the configuration settings."""
251
+ if self.backend not in ["memory", "iris"]:
252
+ logger.error(
253
+ f"Invalid cache backend: {self.backend}. Supported backends: memory, iris"
254
+ )
255
+ return False
256
+
257
+ if self.ttl_seconds <= 0:
258
+ logger.error(f"Invalid TTL: {self.ttl_seconds}")
259
+ return False
260
+
261
+ if self.max_cache_size <= 0:
262
+ logger.error(f"Invalid max cache size: {self.max_cache_size}")
263
+ return False
264
+
265
+ if self.hash_algorithm not in ["sha256", "md5", "sha1"]:
266
+ logger.error(f"Invalid hash algorithm: {self.hash_algorithm}")
267
+ return False
268
+
269
+ return True
270
+
271
+ def to_dict(self) -> Dict[str, Any]:
272
+ """Convert configuration to dictionary."""
273
+ return {
274
+ "enabled": self.enabled,
275
+ "backend": self.backend,
276
+ "ttl_seconds": self.ttl_seconds,
277
+ "normalize_prompts": self.normalize_prompts,
278
+ "max_cache_size": self.max_cache_size,
279
+ "table_name": self.table_name,
280
+ "iris_schema": self.iris_schema,
281
+ "connection_timeout": self.connection_timeout,
282
+ "redis_url": self.redis_url,
283
+ "redis_prefix": self.redis_prefix,
284
+ "monitoring_enabled": self.monitoring_enabled,
285
+ "graceful_fallback": self.graceful_fallback,
286
+ }
287
+
288
+
289
+ def load_cache_config(config_path: Optional[str] = None) -> CacheConfig:
290
+ """
291
+ Load cache configuration from YAML file with environment overrides.
292
+
293
+ Args:
294
+ config_path: Path to YAML config file. Defaults to 'config/cache_config.yaml'
295
+
296
+ Returns:
297
+ CacheConfig instance
298
+ """
299
+ if config_path is None:
300
+ config_path = "config/cache_config.yaml"
301
+
302
+ config = CacheConfig.from_yaml(config_path)
303
+
304
+ if not config.validate():
305
+ logger.warning("Cache configuration validation failed. Using safe defaults.")
306
+ config = CacheConfig() # Use defaults
307
+
308
+ return config