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
common/utils.py
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# common/utils.py
|
|
2
|
+
import logging # Added for logger usage in get_llm_func
|
|
3
|
+
|
|
4
|
+
# import sqlalchemy # Removed - not needed for IRIS
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from pathlib import Path # Added for config path
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple # Added Tuple
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import yaml # Added for config loading
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__) # Added for logger usage
|
|
15
|
+
|
|
16
|
+
# --- Config Loading ---
|
|
17
|
+
_config = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_config():
|
|
21
|
+
global _config
|
|
22
|
+
if _config is None:
|
|
23
|
+
try:
|
|
24
|
+
config_path = Path(__file__).parent.parent / "config" / "config.yaml"
|
|
25
|
+
with open(config_path, "r") as f:
|
|
26
|
+
_config = yaml.safe_load(f)
|
|
27
|
+
logger.info(f"Successfully loaded config from {config_path}")
|
|
28
|
+
except Exception as e:
|
|
29
|
+
logger.error(
|
|
30
|
+
f"Failed to load config/config.yaml: {e}. Using default values."
|
|
31
|
+
)
|
|
32
|
+
_config = {} # Ensure _config is not None
|
|
33
|
+
return _config
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_config_value(key_path: str, default: Any = None) -> Any:
|
|
37
|
+
config = load_config()
|
|
38
|
+
keys = key_path.split(".")
|
|
39
|
+
value = config
|
|
40
|
+
try:
|
|
41
|
+
for key in keys:
|
|
42
|
+
value = value[key]
|
|
43
|
+
return value
|
|
44
|
+
except (KeyError, TypeError):
|
|
45
|
+
logger.warning(
|
|
46
|
+
f"Config key '{key_path}' not found. Returning default: {default}"
|
|
47
|
+
)
|
|
48
|
+
return default
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# --- Dataclasses ---
|
|
52
|
+
@dataclass
|
|
53
|
+
class Document:
|
|
54
|
+
id: str
|
|
55
|
+
content: str
|
|
56
|
+
score: Optional[float] = None # For similarity score from retrieval
|
|
57
|
+
metadata: Optional[Dict[str, Any]] = None # For title or other metadata
|
|
58
|
+
embedding: Optional[List[float]] = field(
|
|
59
|
+
default=None, repr=False
|
|
60
|
+
) # Standard document embedding
|
|
61
|
+
|
|
62
|
+
def to_dict(self, include_embeddings: bool = False) -> Dict[str, Any]:
|
|
63
|
+
data = {
|
|
64
|
+
"id": self.id,
|
|
65
|
+
"content": self.content,
|
|
66
|
+
"score": float(self.score) if self.score is not None else None,
|
|
67
|
+
}
|
|
68
|
+
if include_embeddings:
|
|
69
|
+
data["embedding"] = self.embedding
|
|
70
|
+
return data
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# --- Model and Connector Wrappers ---
|
|
74
|
+
|
|
75
|
+
_llm_instance = None
|
|
76
|
+
_current_llm_key = None # For caching LLM instance
|
|
77
|
+
|
|
78
|
+
DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
|
79
|
+
DEFAULT_EMBEDDING_DIMENSION = 384
|
|
80
|
+
|
|
81
|
+
# New pure HuggingFace embedder
|
|
82
|
+
_hf_embedder_cache = {}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_hf_embedder(model_name: str):
|
|
86
|
+
"""
|
|
87
|
+
Builds an embedding function using HuggingFace transformers directly.
|
|
88
|
+
Includes tokenization, model inference, mean pooling, and normalization.
|
|
89
|
+
Caches tokenizer and model per model_name.
|
|
90
|
+
"""
|
|
91
|
+
global _hf_embedder_cache
|
|
92
|
+
import torch
|
|
93
|
+
|
|
94
|
+
if model_name not in _hf_embedder_cache:
|
|
95
|
+
from common.huggingface_utils import download_huggingface_model
|
|
96
|
+
|
|
97
|
+
print(f"Initializing HF embedder for model: {model_name}")
|
|
98
|
+
tokenizer, model = download_huggingface_model(model_name)
|
|
99
|
+
model.eval() # Set to evaluation mode
|
|
100
|
+
# Consider model.to(device) if GPU is available/desired
|
|
101
|
+
_hf_embedder_cache[model_name] = (tokenizer, model)
|
|
102
|
+
else:
|
|
103
|
+
print(f"Using cached HF embedder for model: {model_name}")
|
|
104
|
+
|
|
105
|
+
tokenizer, model = _hf_embedder_cache[model_name]
|
|
106
|
+
|
|
107
|
+
# Removed LRU cache to prevent memory leaks during bulk operations
|
|
108
|
+
# Cache causes indefinite memory growth when processing thousands of unique texts
|
|
109
|
+
def _embed_single_text(text: str) -> List[float]:
|
|
110
|
+
with torch.no_grad():
|
|
111
|
+
# Validate input text
|
|
112
|
+
if not text or not text.strip():
|
|
113
|
+
logger.warning("Empty or whitespace-only text provided for embedding")
|
|
114
|
+
# Use configured dimension for zero vector
|
|
115
|
+
dimension = get_config_value(
|
|
116
|
+
"embedding_model.dimension", DEFAULT_EMBEDDING_DIMENSION
|
|
117
|
+
)
|
|
118
|
+
return [0.0] * dimension
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
inputs = tokenizer(
|
|
122
|
+
text,
|
|
123
|
+
return_tensors="pt",
|
|
124
|
+
truncation=True,
|
|
125
|
+
padding=True,
|
|
126
|
+
max_length=512,
|
|
127
|
+
)
|
|
128
|
+
outputs = model(**inputs)
|
|
129
|
+
token_embeddings = outputs.last_hidden_state
|
|
130
|
+
attention_mask = inputs.attention_mask
|
|
131
|
+
input_mask_expanded = (
|
|
132
|
+
attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()
|
|
133
|
+
)
|
|
134
|
+
sum_embeddings = torch.sum(token_embeddings * input_mask_expanded, 1)
|
|
135
|
+
sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
|
|
136
|
+
pooled_embedding = sum_embeddings / sum_mask
|
|
137
|
+
normalized_embedding = torch.nn.functional.normalize(
|
|
138
|
+
pooled_embedding, p=2, dim=1
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Convert to numpy for NaN/inf checking
|
|
142
|
+
embedding_array = normalized_embedding[0].cpu().numpy()
|
|
143
|
+
|
|
144
|
+
# Check for NaN or inf values and fix them
|
|
145
|
+
if np.any(np.isnan(embedding_array)) or np.any(
|
|
146
|
+
np.isinf(embedding_array)
|
|
147
|
+
):
|
|
148
|
+
logger.warning(
|
|
149
|
+
f"NaN/inf values detected in embedding for text: {text[:50]}..."
|
|
150
|
+
)
|
|
151
|
+
embedding_array = np.nan_to_num(
|
|
152
|
+
embedding_array, nan=0.0, posinf=1.0, neginf=-1.0
|
|
153
|
+
)
|
|
154
|
+
# Re-normalize after fixing
|
|
155
|
+
norm = np.linalg.norm(embedding_array)
|
|
156
|
+
if norm > 0:
|
|
157
|
+
embedding_array = embedding_array / norm
|
|
158
|
+
else:
|
|
159
|
+
embedding_array = np.zeros_like(embedding_array)
|
|
160
|
+
|
|
161
|
+
return embedding_array.tolist()
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(
|
|
165
|
+
f"Error generating embedding for text '{text[:50]}...': {e}"
|
|
166
|
+
)
|
|
167
|
+
dimension = get_config_value(
|
|
168
|
+
"embedding_model.dimension", DEFAULT_EMBEDDING_DIMENSION
|
|
169
|
+
)
|
|
170
|
+
return [0.0] * dimension
|
|
171
|
+
|
|
172
|
+
def embedding_func_hf(texts: List[str]) -> List[List[float]]:
|
|
173
|
+
# Add memory pressure detection for bulk operations
|
|
174
|
+
if len(texts) > 10: # Bulk operation detected
|
|
175
|
+
try:
|
|
176
|
+
import gc
|
|
177
|
+
|
|
178
|
+
import psutil
|
|
179
|
+
|
|
180
|
+
process = psutil.Process()
|
|
181
|
+
memory_mb = process.memory_info().rss / 1024 / 1024
|
|
182
|
+
|
|
183
|
+
# If memory usage exceeds 2GB, force cleanup
|
|
184
|
+
if memory_mb > 2048:
|
|
185
|
+
logger.warning(
|
|
186
|
+
f"High memory usage detected: {memory_mb:.1f} MB. Forcing cleanup..."
|
|
187
|
+
)
|
|
188
|
+
gc.collect()
|
|
189
|
+
# Clear torch cache if available
|
|
190
|
+
try:
|
|
191
|
+
import torch
|
|
192
|
+
|
|
193
|
+
if torch.cuda.is_available():
|
|
194
|
+
torch.cuda.empty_cache()
|
|
195
|
+
except ImportError:
|
|
196
|
+
pass
|
|
197
|
+
except ImportError:
|
|
198
|
+
pass # psutil not available
|
|
199
|
+
|
|
200
|
+
return [_embed_single_text(t) for t in texts]
|
|
201
|
+
|
|
202
|
+
return embedding_func_hf
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_embedding_func(
|
|
206
|
+
model_name_override: Optional[str] = None,
|
|
207
|
+
provider: Optional[str] = None,
|
|
208
|
+
mock: bool = False,
|
|
209
|
+
) -> Callable:
|
|
210
|
+
"""
|
|
211
|
+
Returns a function that takes either a single text string or a list of texts and returns embeddings.
|
|
212
|
+
Reads model name from config/config.yaml, with override option.
|
|
213
|
+
Supports a "stub" provider or mock=True for testing without real models.
|
|
214
|
+
|
|
215
|
+
The returned function handles both:
|
|
216
|
+
- Single string: embedding_func("text") -> List[float]
|
|
217
|
+
- List of strings: embedding_func(["text1", "text2"]) -> List[List[float]]
|
|
218
|
+
"""
|
|
219
|
+
effective_model_name = model_name_override or get_config_value(
|
|
220
|
+
"embedding_model.name", DEFAULT_EMBEDDING_MODEL_NAME
|
|
221
|
+
)
|
|
222
|
+
dimension = get_config_value(
|
|
223
|
+
"embedding_model.dimension", DEFAULT_EMBEDDING_DIMENSION
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if mock or provider == "stub" or effective_model_name == "stub":
|
|
227
|
+
logger.info(f"Using stub embedding function with dimension {dimension}.")
|
|
228
|
+
|
|
229
|
+
def stub_embed_texts(texts) -> List[List[float]]:
|
|
230
|
+
# Handle both single string and list of strings
|
|
231
|
+
if isinstance(texts, str):
|
|
232
|
+
return [(len(texts) % 100) * 0.01] * dimension
|
|
233
|
+
return [[(len(text) % 100) * 0.01] * dimension for text in texts]
|
|
234
|
+
|
|
235
|
+
return stub_embed_texts
|
|
236
|
+
|
|
237
|
+
logger.info(f"Using pure HuggingFace embedder for model: {effective_model_name}")
|
|
238
|
+
base_embedder = build_hf_embedder(effective_model_name)
|
|
239
|
+
|
|
240
|
+
def flexible_embedder(texts):
|
|
241
|
+
"""Wrapper that handles both single strings and lists of strings"""
|
|
242
|
+
if isinstance(texts, str):
|
|
243
|
+
# Single string input - return single embedding
|
|
244
|
+
result = base_embedder([texts])
|
|
245
|
+
return result[0] if result else [0.0] * dimension
|
|
246
|
+
else:
|
|
247
|
+
# List input - return list of embeddings
|
|
248
|
+
return base_embedder(texts)
|
|
249
|
+
|
|
250
|
+
return flexible_embedder
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_llm_func(
|
|
254
|
+
provider: str = "openai",
|
|
255
|
+
model_name: str = "gpt-4.1-mini",
|
|
256
|
+
enable_cache: Optional[bool] = None,
|
|
257
|
+
**kwargs,
|
|
258
|
+
) -> Callable[[str], str]:
|
|
259
|
+
"""
|
|
260
|
+
Returns a function that takes a prompt string and returns an LLM completion string.
|
|
261
|
+
Supports 'openai' or a 'stub' for testing.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
provider: LLM provider (openai, stub, etc.)
|
|
265
|
+
model_name: Model name
|
|
266
|
+
enable_cache: Override cache enable/disable (None = use config default)
|
|
267
|
+
**kwargs: Additional LLM parameters
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
LLM function (automatically cached if enabled)
|
|
271
|
+
"""
|
|
272
|
+
global _llm_instance, _current_llm_key
|
|
273
|
+
|
|
274
|
+
# Setup caching if enabled
|
|
275
|
+
cache_enabled = _setup_caching_if_needed(enable_cache)
|
|
276
|
+
|
|
277
|
+
llm_key = f"{provider}_{model_name}_{cache_enabled}"
|
|
278
|
+
|
|
279
|
+
if provider == "openai" and not os.getenv("OPENAI_API_KEY"):
|
|
280
|
+
try:
|
|
281
|
+
from dotenv import find_dotenv, load_dotenv
|
|
282
|
+
|
|
283
|
+
project_root = os.path.abspath(
|
|
284
|
+
os.path.join(os.path.dirname(__file__), "..")
|
|
285
|
+
)
|
|
286
|
+
dotenv_path = os.path.join(project_root, ".env")
|
|
287
|
+
if os.path.exists(dotenv_path):
|
|
288
|
+
logger.info(
|
|
289
|
+
f"Attempting to load .env file from {dotenv_path} in get_llm_func."
|
|
290
|
+
)
|
|
291
|
+
load_dotenv(dotenv_path=dotenv_path, override=True)
|
|
292
|
+
else:
|
|
293
|
+
env_path_found = find_dotenv(usecwd=True)
|
|
294
|
+
if env_path_found:
|
|
295
|
+
logger.info(
|
|
296
|
+
f"Attempting to load .env file from found path {env_path_found} in get_llm_func."
|
|
297
|
+
)
|
|
298
|
+
load_dotenv(dotenv_path=env_path_found, override=True)
|
|
299
|
+
else:
|
|
300
|
+
logger.warning(
|
|
301
|
+
"No .env file found by find_dotenv() in get_llm_func."
|
|
302
|
+
)
|
|
303
|
+
except ImportError:
|
|
304
|
+
if not logger.hasHandlers():
|
|
305
|
+
logging.basicConfig(level=logging.INFO)
|
|
306
|
+
logger.warning(
|
|
307
|
+
"python-dotenv not installed. Cannot load .env file in get_llm_func."
|
|
308
|
+
)
|
|
309
|
+
except Exception as e_dotenv:
|
|
310
|
+
if not logger.hasHandlers():
|
|
311
|
+
logging.basicConfig(level=logging.INFO)
|
|
312
|
+
logger.warning(f"Error loading .env file in get_llm_func: {e_dotenv}")
|
|
313
|
+
|
|
314
|
+
if _llm_instance is None or _current_llm_key != llm_key:
|
|
315
|
+
print(f"Initializing LLM: {provider} - {model_name}")
|
|
316
|
+
if provider == "openai":
|
|
317
|
+
try:
|
|
318
|
+
from langchain_openai import ChatOpenAI
|
|
319
|
+
except ImportError:
|
|
320
|
+
raise ImportError(
|
|
321
|
+
"LangChain OpenAI library not found. Please install with `poetry add langchain-openai`."
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
|
325
|
+
if not api_key:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
"OPENAI_API_KEY environment variable not set or found in .env file."
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
_llm_instance = ChatOpenAI(
|
|
331
|
+
model_name=model_name, openai_api_key=api_key, **kwargs
|
|
332
|
+
)
|
|
333
|
+
_current_llm_key = llm_key
|
|
334
|
+
|
|
335
|
+
elif provider == "stub":
|
|
336
|
+
|
|
337
|
+
class StubLLM:
|
|
338
|
+
def __init__(self, model_name, **kwargs):
|
|
339
|
+
self.model_name = model_name
|
|
340
|
+
|
|
341
|
+
def invoke(self, prompt: str, **kwargs) -> Any:
|
|
342
|
+
response_content = (
|
|
343
|
+
f"Stub LLM response for prompt: '{prompt[:50]}...'"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
class AIMessage:
|
|
347
|
+
def __init__(self, content):
|
|
348
|
+
self.content = content
|
|
349
|
+
|
|
350
|
+
return AIMessage(content=response_content)
|
|
351
|
+
|
|
352
|
+
_llm_instance = StubLLM(model_name=model_name, **kwargs)
|
|
353
|
+
_current_llm_key = llm_key
|
|
354
|
+
else:
|
|
355
|
+
raise ValueError(f"Unsupported LLM provider: {provider}")
|
|
356
|
+
|
|
357
|
+
def query_llm(prompt: str) -> str:
|
|
358
|
+
response = _llm_instance.invoke(prompt)
|
|
359
|
+
# Ensure the response is always a string
|
|
360
|
+
if hasattr(response, "content"):
|
|
361
|
+
return str(response.content)
|
|
362
|
+
return str(response)
|
|
363
|
+
|
|
364
|
+
return query_llm
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def get_iris_connector(db_url: Optional[str] = None):
|
|
368
|
+
if db_url is None:
|
|
369
|
+
db_url = os.getenv("IRIS_CONNECTION_URL")
|
|
370
|
+
if not db_url:
|
|
371
|
+
raise ValueError(
|
|
372
|
+
"IRIS_CONNECTION_URL environment variable not set and db_url not provided."
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
print(f"Connecting to IRIS at: {db_url}")
|
|
376
|
+
try:
|
|
377
|
+
import sqlalchemy
|
|
378
|
+
|
|
379
|
+
engine = sqlalchemy.create_engine(db_url)
|
|
380
|
+
connection = engine.connect()
|
|
381
|
+
return connection
|
|
382
|
+
except Exception as e:
|
|
383
|
+
print(f"Failed to connect to IRIS: {e}")
|
|
384
|
+
raise
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def timing_decorator(func: Callable) -> Callable:
|
|
388
|
+
def wrapper(*args, **kwargs):
|
|
389
|
+
start_time = time.perf_counter()
|
|
390
|
+
result = func(*args, **kwargs)
|
|
391
|
+
end_time = time.perf_counter()
|
|
392
|
+
print(
|
|
393
|
+
f"Function {func.__name__} executed in {end_time - start_time:.4f} seconds"
|
|
394
|
+
)
|
|
395
|
+
if isinstance(result, dict) and "latency_ms" not in result:
|
|
396
|
+
result["latency_ms"] = (end_time - start_time) * 1000
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
return wrapper
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# ... (Embedded Python specific utilities can remain as they are) ...
|
|
403
|
+
|
|
404
|
+
_iris_connector_embedded = None
|
|
405
|
+
_embedding_model_embedded = None
|
|
406
|
+
_llm_embedded = None
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def get_iris_connector_for_embedded():
|
|
410
|
+
global _iris_connector_embedded
|
|
411
|
+
if _iris_connector_embedded is None:
|
|
412
|
+
try:
|
|
413
|
+
try:
|
|
414
|
+
import iris
|
|
415
|
+
except ImportError:
|
|
416
|
+
raise ImportError(
|
|
417
|
+
"IRIS Embedded Python module 'iris' not found. Ensure it is installed in your environment."
|
|
418
|
+
)
|
|
419
|
+
_iris_connector_embedded = iris.connect()
|
|
420
|
+
print("IRIS Embedded Python: DBAPI connection established.")
|
|
421
|
+
except ImportError:
|
|
422
|
+
print("IRIS Embedded Python: 'iris' module not found.")
|
|
423
|
+
_iris_connector_embedded = None
|
|
424
|
+
except Exception as e:
|
|
425
|
+
print(f"IRIS Embedded Python: Error connecting to DB: {e}")
|
|
426
|
+
_iris_connector_embedded = None
|
|
427
|
+
return _iris_connector_embedded
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def get_embedding_func_for_embedded(model_name_override: Optional[str] = None):
|
|
431
|
+
global _embedding_model_embedded
|
|
432
|
+
if _embedding_model_embedded is None:
|
|
433
|
+
effective_model_name = model_name_override or get_config_value(
|
|
434
|
+
"embedding_model.name", DEFAULT_EMBEDDING_MODEL_NAME
|
|
435
|
+
)
|
|
436
|
+
dimension = get_config_value(
|
|
437
|
+
"embedding_model.dimension", DEFAULT_EMBEDDING_DIMENSION
|
|
438
|
+
)
|
|
439
|
+
print(
|
|
440
|
+
f"IRIS Embedded Python: Loading embedding model {effective_model_name} (dim: {dimension})"
|
|
441
|
+
)
|
|
442
|
+
# This would call build_hf_embedder or similar for embedded context
|
|
443
|
+
_embedding_model_embedded = lambda texts: [[0.1] * dimension for _ in texts]
|
|
444
|
+
return _embedding_model_embedded
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def get_llm_func_for_embedded(provider: str = "stub", model_name: str = "stub-model"):
|
|
448
|
+
global _llm_embedded
|
|
449
|
+
if _llm_embedded is None:
|
|
450
|
+
print(f"IRIS Embedded Python: Initializing LLM {provider} - {model_name}")
|
|
451
|
+
if provider == "stub":
|
|
452
|
+
_llm_embedded = lambda prompt: f"Embedded Stub LLM: {prompt[:30]}"
|
|
453
|
+
else:
|
|
454
|
+
_llm_embedded = lambda prompt: "Error: LLM not configured for embedded"
|
|
455
|
+
return _llm_embedded
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
if __name__ == "__main__":
|
|
459
|
+
print("Testing common.utils...")
|
|
460
|
+
doc = Document(id="test_doc_001", content="This is a test document.")
|
|
461
|
+
print(f"Created Document: {doc}")
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
embed_func = get_embedding_func()
|
|
465
|
+
config_model_name = get_config_value(
|
|
466
|
+
"embedding_model.name", DEFAULT_EMBEDDING_MODEL_NAME
|
|
467
|
+
)
|
|
468
|
+
config_dimension = get_config_value(
|
|
469
|
+
"embedding_model.dimension", DEFAULT_EMBEDDING_DIMENSION
|
|
470
|
+
)
|
|
471
|
+
print(
|
|
472
|
+
f"Testing with embedding model: {config_model_name}, dimension: {config_dimension}"
|
|
473
|
+
)
|
|
474
|
+
sample_texts = ["Hello world", "This is a test."]
|
|
475
|
+
embeddings = embed_func(sample_texts)
|
|
476
|
+
print(
|
|
477
|
+
f"Embeddings generated for {len(embeddings)} texts. First embedding dim: {len(embeddings[0]) if embeddings else 'N/A'}"
|
|
478
|
+
)
|
|
479
|
+
assert len(embeddings) == 2
|
|
480
|
+
if (
|
|
481
|
+
embeddings and embeddings[0] is not None
|
|
482
|
+
): # Check if embedding was successful
|
|
483
|
+
assert len(embeddings[0]) == config_dimension
|
|
484
|
+
except ImportError as e:
|
|
485
|
+
print(f"Skipping embedding test: {e}")
|
|
486
|
+
except Exception as e:
|
|
487
|
+
print(f"Error during embedding test: {e}")
|
|
488
|
+
|
|
489
|
+
try:
|
|
490
|
+
llm_stub_func = get_llm_func(provider="stub")
|
|
491
|
+
response_stub = llm_stub_func("Test prompt for stub LLM")
|
|
492
|
+
print(f"Stub LLM Response: {response_stub}")
|
|
493
|
+
assert "Stub LLM response" in response_stub
|
|
494
|
+
except Exception as e:
|
|
495
|
+
print(f"Error during stub LLM test: {e}")
|
|
496
|
+
|
|
497
|
+
if os.getenv("OPENAI_API_KEY"):
|
|
498
|
+
try:
|
|
499
|
+
llm_openai_func = get_llm_func(
|
|
500
|
+
provider="openai", model_name="gpt-3.5-turbo"
|
|
501
|
+
)
|
|
502
|
+
response_openai = llm_openai_func("What is 1+1?")
|
|
503
|
+
print(f"OpenAI LLM Response: {response_openai}")
|
|
504
|
+
assert response_openai is not None
|
|
505
|
+
except ImportError as e:
|
|
506
|
+
print(f"Skipping OpenAI LLM test: {e}")
|
|
507
|
+
except ValueError as e:
|
|
508
|
+
print(f"Skipping OpenAI LLM test due to config error: {e}")
|
|
509
|
+
except Exception as e:
|
|
510
|
+
print(f"Error during OpenAI LLM test: {e}")
|
|
511
|
+
else:
|
|
512
|
+
print("Skipping OpenAI LLM test: OPENAI_API_KEY not set.")
|
|
513
|
+
|
|
514
|
+
@timing_decorator
|
|
515
|
+
def example_timed_function(duration):
|
|
516
|
+
time.sleep(duration)
|
|
517
|
+
return {"status": "complete", "slept_for": duration}
|
|
518
|
+
|
|
519
|
+
timed_result = example_timed_function(0.1)
|
|
520
|
+
print(f"Timed function result: {timed_result}")
|
|
521
|
+
assert "latency_ms" in timed_result
|
|
522
|
+
assert timed_result["latency_ms"] > 0
|
|
523
|
+
|
|
524
|
+
print("common.utils tests finished.")
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _setup_caching_if_needed(enable_cache: Optional[bool] = None) -> bool:
|
|
528
|
+
"""
|
|
529
|
+
Setup LLM caching if needed and return whether caching is enabled.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
enable_cache: Override cache enable/disable (None = use config default)
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
True if caching is enabled, False otherwise
|
|
536
|
+
"""
|
|
537
|
+
try:
|
|
538
|
+
from common.llm_cache_config import load_cache_config
|
|
539
|
+
from common.llm_cache_manager import (
|
|
540
|
+
is_langchain_cache_configured,
|
|
541
|
+
setup_langchain_cache,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# Load configuration
|
|
545
|
+
config = load_cache_config()
|
|
546
|
+
|
|
547
|
+
# Determine if caching should be enabled
|
|
548
|
+
cache_enabled = enable_cache if enable_cache is not None else config.enabled
|
|
549
|
+
|
|
550
|
+
# Setup cache if enabled and not already configured
|
|
551
|
+
if cache_enabled and not is_langchain_cache_configured():
|
|
552
|
+
setup_langchain_cache(config)
|
|
553
|
+
logger.info("LLM caching initialized")
|
|
554
|
+
|
|
555
|
+
return cache_enabled
|
|
556
|
+
|
|
557
|
+
except Exception as e:
|
|
558
|
+
logger.warning(f"Failed to setup LLM caching: {e}")
|
|
559
|
+
return False
|