TriCacheLLM-MMA 0.1.0__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.
- portable_cache_Ai/portable_cache_rerankAi.py +132 -0
- portable_cache_bgWorkers/portable_cache_celery_conf.py +51 -0
- portable_cache_bgWorkers/portable_cache_workers.py +163 -0
- portable_cache_schemas/portable_cache_dbBase.py +3 -0
- portable_cache_schemas/portable_cache_dbConf.py +147 -0
- portable_cache_schemas/portable_cache_schemas.py +8 -0
- portable_cache_utils/portable_cache_embedding_model.py +7 -0
- portable_cache_utils/protable_cache_DynamicEnv_maker.py +152 -0
- tricachellm_mma-0.1.0.dist-info/METADATA +949 -0
- tricachellm_mma-0.1.0.dist-info/RECORD +13 -0
- tricachellm_mma-0.1.0.dist-info/WHEEL +5 -0
- tricachellm_mma-0.1.0.dist-info/licenses/LICENSE +73 -0
- tricachellm_mma-0.1.0.dist-info/top_level.txt +4 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# File: portable_cache_Ai/portable_cache_rerankAi.py
|
|
2
|
+
import cohere
|
|
3
|
+
from typing import Optional, Any
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from sqlalchemy import create_engine
|
|
6
|
+
from sqlalchemy.orm import sessionmaker
|
|
7
|
+
from langchain_core.documents import Document as LangChainDocument
|
|
8
|
+
from portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
|
|
9
|
+
from portable_cache_dbSchema import Paths
|
|
10
|
+
|
|
11
|
+
class CohereManager:
|
|
12
|
+
"""Encapsulates the Cohere client to prevent import-time crashes."""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self._client: Optional[cohere.ClientV2] = None
|
|
15
|
+
self._current_api_key: Optional[str] = None
|
|
16
|
+
|
|
17
|
+
def _get_cohere_api_key_from_registry(self) -> Optional[str]:
|
|
18
|
+
"""Sneaks a peek into the SQLite registry DB to grab the true source-of-truth cohere_api_key."""
|
|
19
|
+
try:
|
|
20
|
+
settings = get_settings()
|
|
21
|
+
db_path = Path(settings.portable_cache_registry_db)
|
|
22
|
+
if not db_path.exists():
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
sync_engine = create_engine(f"sqlite:///{db_path}")
|
|
26
|
+
SessionLocal = sessionmaker(bind=sync_engine)
|
|
27
|
+
with SessionLocal() as session:
|
|
28
|
+
path_record = session.query(Paths).filter_by(id=1).first()
|
|
29
|
+
|
|
30
|
+
sync_engine.dispose()
|
|
31
|
+
if path_record and path_record.cohere_api_key:
|
|
32
|
+
return path_record.cohere_api_key
|
|
33
|
+
except Exception:
|
|
34
|
+
pass
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
def get_client(self, api_key: Optional[str] = None) -> cohere.ClientV2:
|
|
38
|
+
"""Lazily initializes the Cohere V2 client using explicit arg, registry DB, or settings/env."""
|
|
39
|
+
settings = get_settings()
|
|
40
|
+
|
|
41
|
+
target_api_key = api_key or self._get_cohere_api_key_from_registry() or settings.portable_cache_cohere_api_key
|
|
42
|
+
|
|
43
|
+
if not target_api_key:
|
|
44
|
+
raise ValueError("Cohere API key not found! Run create_cache_system first or set your API key.")
|
|
45
|
+
|
|
46
|
+
if self._client is None or self._current_api_key != target_api_key:
|
|
47
|
+
self._current_api_key = target_api_key
|
|
48
|
+
self._client = cohere.ClientV2(api_key=target_api_key)
|
|
49
|
+
|
|
50
|
+
return self._client
|
|
51
|
+
|
|
52
|
+
cohere_manager = CohereManager()
|
|
53
|
+
|
|
54
|
+
async def portable_cache_cohere_rerank(
|
|
55
|
+
question: str,
|
|
56
|
+
user_id: Any,
|
|
57
|
+
received_docs: list[LangChainDocument],
|
|
58
|
+
top_k: int,
|
|
59
|
+
) -> dict:
|
|
60
|
+
"""
|
|
61
|
+
Enterprise portable wrapper for document reranking using Cohere's native cross-encoder API.
|
|
62
|
+
Returns a clean dict: {"success": bool, "data": list[LangChainDocument] | None, "error": str | None}
|
|
63
|
+
"""
|
|
64
|
+
print(f"CACHE_RERANK_STARTED where user_id: {user_id}")
|
|
65
|
+
|
|
66
|
+
if not question or not question.strip():
|
|
67
|
+
print(f"CACHE_RERANK_FAILED: Empty input question for user_id: {user_id}")
|
|
68
|
+
return {
|
|
69
|
+
"success": False,
|
|
70
|
+
"data": None,
|
|
71
|
+
"error": "Input text is empty"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if not received_docs:
|
|
75
|
+
print(f"CACHE_RERANK_FAILED: No documents received for user_id: {user_id}")
|
|
76
|
+
return {
|
|
77
|
+
"success": False,
|
|
78
|
+
"data": None,
|
|
79
|
+
"error": "Re-ranker got no data, meaning retriever returned empty candidates."
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
documents_text = [doc.page_content for doc in received_docs]
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
print(f"CACHE_RERANK_PROVIDER_REQUEST where user_id: {user_id}")
|
|
86
|
+
client = cohere_manager.get_client()
|
|
87
|
+
rerank_model = get_settings().portable_cache_cohere_rerank_model
|
|
88
|
+
|
|
89
|
+
response = client.rerank(
|
|
90
|
+
model=rerank_model,
|
|
91
|
+
query=question,
|
|
92
|
+
documents=documents_text,
|
|
93
|
+
top_n=min(top_k, len(documents_text)),
|
|
94
|
+
)
|
|
95
|
+
print(f"CACHE_RERANK_PROVIDER_SUCCESS where user_id: {user_id}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"CACHE_RERANK_ERROR where error: {str(e)} and user_id: {user_id}")
|
|
98
|
+
return {
|
|
99
|
+
"success": False,
|
|
100
|
+
"data": None,
|
|
101
|
+
"error": str(e)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
print(f"CACHE_RERANK_MAPPING_STARTED where user_id: {user_id}")
|
|
105
|
+
reranked_docs: list[LangChainDocument] = []
|
|
106
|
+
try:
|
|
107
|
+
for result in response.results:
|
|
108
|
+
original_doc = received_docs[result.index]
|
|
109
|
+
updated_metadata = dict(original_doc.metadata or {})
|
|
110
|
+
updated_metadata["rerank_score"] = float(result.relevance_score)
|
|
111
|
+
|
|
112
|
+
reranked_docs.append(
|
|
113
|
+
LangChainDocument(
|
|
114
|
+
page_content=original_doc.page_content,
|
|
115
|
+
metadata=updated_metadata,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
print(f"CACHE_RERANK_MAPPING_SUCCESS where user_id: {user_id}")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"CACHE_RERANK_MAPPING_ERROR where error: {str(e)} and user_id: {user_id}")
|
|
121
|
+
return {
|
|
122
|
+
"success": False,
|
|
123
|
+
"data": None,
|
|
124
|
+
"error": "Failed to map reranked results to documents."
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
print(f"CACHE_RERANK_COMPLETED where user_id: {user_id}")
|
|
128
|
+
return {
|
|
129
|
+
"success": True,
|
|
130
|
+
"data": reranked_docs,
|
|
131
|
+
"error": None
|
|
132
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# File: portable_cache_bgWorkers/portable_cache_celery_conf.py
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from celery import Celery
|
|
4
|
+
from sqlalchemy import create_engine
|
|
5
|
+
from sqlalchemy.orm import sessionmaker
|
|
6
|
+
from portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
|
|
7
|
+
from portable_cache_dbSchema import Paths
|
|
8
|
+
|
|
9
|
+
def get_celery_redis_url() -> str:
|
|
10
|
+
"""Sneaks a peek into the SQLite registry DB to grab the true source-of-truth redis_url for Celery."""
|
|
11
|
+
try:
|
|
12
|
+
settings = get_settings()
|
|
13
|
+
if settings.protable_cache_registry_db:
|
|
14
|
+
db_path = Path(settings.protable_cache_registry_db)
|
|
15
|
+
if db_path.exists():
|
|
16
|
+
sync_engine = create_engine(f"sqlite:///{db_path}")
|
|
17
|
+
SessionLocal = sessionmaker(bind=sync_engine)
|
|
18
|
+
with SessionLocal() as session:
|
|
19
|
+
path_record = session.query(Paths).filter_by(id=1).first()
|
|
20
|
+
sync_engine.dispose()
|
|
21
|
+
if path_record and path_record.redis_url:
|
|
22
|
+
return path_record.redis_url
|
|
23
|
+
except Exception:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
return get_settings().portable_cache_redis_url
|
|
27
|
+
|
|
28
|
+
redis_base_url = get_celery_redis_url()
|
|
29
|
+
|
|
30
|
+
celery_app = Celery(
|
|
31
|
+
"fastapi_ai_backend",
|
|
32
|
+
broker=redis_base_url,
|
|
33
|
+
backend=redis_base_url.rsplit("/", 1)[0] + "/1",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
celery_app.conf.imports = (
|
|
37
|
+
"portable_cache_bgWorkers.portable_cache_workers",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
celery_app.conf.task_default_queue = "default"
|
|
41
|
+
celery_app.conf.task_routes = {
|
|
42
|
+
"ai.*": {
|
|
43
|
+
"queue": "ai"
|
|
44
|
+
},
|
|
45
|
+
"retri.*": {
|
|
46
|
+
"queue": "retri"
|
|
47
|
+
},
|
|
48
|
+
"maintenance.*": {
|
|
49
|
+
"queue": "maintenance"
|
|
50
|
+
},
|
|
51
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# File: portable_cache_bgWorkers/portable_cache_workers.py
|
|
2
|
+
import asyncio
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
from portable_cache_bgWorkers.portable_cache_celery_conf import celery_app
|
|
6
|
+
from portable_cache_schemas.portable_cache_dbConf import db_manager
|
|
7
|
+
import json
|
|
8
|
+
from portable_cache_dbSchema import CacheVDBResource
|
|
9
|
+
from portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
|
|
10
|
+
from portable_cache_schemas.portable_cache_schemas import CacheVDBStatus
|
|
11
|
+
from langchain_core.documents import Document as LangChainDocument
|
|
12
|
+
from sqlalchemy import select
|
|
13
|
+
from portable_cache_utils.portable_cache_embedding_model import embedding_model
|
|
14
|
+
from langchain_chroma import Chroma
|
|
15
|
+
from datetime import datetime, timezone
|
|
16
|
+
from portable_cache_dbSchema import Paths
|
|
17
|
+
from sqlalchemy import text
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
@celery_app.task(bind=True, max_retries=3, name="ai.cache_vdb")
|
|
21
|
+
def create_cache_vdb_worker(self, user_id: int):
|
|
22
|
+
try:
|
|
23
|
+
return asyncio.run(
|
|
24
|
+
create_cache_vdb_async(
|
|
25
|
+
task_instance=self,
|
|
26
|
+
user_id=user_id,
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
except Exception as exc:
|
|
30
|
+
raise self.retry(exc=exc, countdown=10)
|
|
31
|
+
|
|
32
|
+
async def create_cache_vdb_async(task_instance: Any, user_id: int):
|
|
33
|
+
async with db_manager.celery_session() as db:
|
|
34
|
+
try:
|
|
35
|
+
result = await db.execute(select(Paths).filter_by(id=1))
|
|
36
|
+
path_record = result.scalars().first()
|
|
37
|
+
|
|
38
|
+
if path_record and path_record.chroma_db_dir:
|
|
39
|
+
base_chroma_dir = path_record.chroma_db_dir
|
|
40
|
+
else:
|
|
41
|
+
base_chroma_dir = get_settings().portable_cache_chroma_db_dir
|
|
42
|
+
|
|
43
|
+
user_cache_dir = Path(base_chroma_dir) / f"user_{user_id}"
|
|
44
|
+
await asyncio.to_thread(
|
|
45
|
+
user_cache_dir.mkdir,
|
|
46
|
+
parents=True,
|
|
47
|
+
exist_ok=True,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
await asyncio.to_thread(
|
|
51
|
+
lambda: Chroma(
|
|
52
|
+
collection_name=f"question_cache_{user_id}",
|
|
53
|
+
embedding_function=embedding_model,
|
|
54
|
+
persist_directory=str(user_cache_dir),
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
stmt = select(CacheVDBResource).where(
|
|
59
|
+
CacheVDBResource.user_id == user_id
|
|
60
|
+
)
|
|
61
|
+
result = await db.execute(stmt)
|
|
62
|
+
cache_res = result.scalar_one_or_none()
|
|
63
|
+
|
|
64
|
+
if cache_res is None:
|
|
65
|
+
raise RuntimeError(
|
|
66
|
+
f"Cache VDB resource record not found in DB for user {user_id}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
cache_res.status = CacheVDBStatus.READY
|
|
70
|
+
cache_res.vdb_path = str(user_cache_dir)
|
|
71
|
+
cache_res.failure_reason = None
|
|
72
|
+
await db.commit()
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"status": "READY",
|
|
76
|
+
"user_id": user_id,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
stmt = select(CacheVDBResource).where(
|
|
81
|
+
CacheVDBResource.user_id == user_id
|
|
82
|
+
)
|
|
83
|
+
result = await db.execute(stmt)
|
|
84
|
+
cache_res = result.scalar_one_or_none()
|
|
85
|
+
|
|
86
|
+
if cache_res:
|
|
87
|
+
cache_res.status = CacheVDBStatus.FAILED
|
|
88
|
+
cache_res.failure_reason = str(exc)
|
|
89
|
+
await db.commit()
|
|
90
|
+
|
|
91
|
+
raise
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@celery_app.task(bind=True, max_retries=3, name="ai.push_cache_vdb")
|
|
95
|
+
def push_responce_in_cache_worker(self, user_id: int, question: str, model_output_dict: dict):
|
|
96
|
+
try:
|
|
97
|
+
return asyncio.run(
|
|
98
|
+
push_response_in_cache_async(
|
|
99
|
+
task_instance=self,
|
|
100
|
+
user_id=user_id,
|
|
101
|
+
question=question,
|
|
102
|
+
model_output_dict=model_output_dict
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
except Exception as exc:
|
|
106
|
+
raise self.retry(exc=exc, countdown=10)
|
|
107
|
+
|
|
108
|
+
async def push_response_in_cache_async(task_instance: Any, user_id, question: str, model_output_dict: dict) -> str | None:
|
|
109
|
+
print(f"CACHE_VDB_PUSH_ASYNC_ENTERED where user_id: {user_id}")
|
|
110
|
+
async with db_manager.celery_session() as db:
|
|
111
|
+
try:
|
|
112
|
+
response_json_str = json.dumps(model_output_dict)
|
|
113
|
+
|
|
114
|
+
cache_metadata = {
|
|
115
|
+
"user_id": user_id,
|
|
116
|
+
"question": question,
|
|
117
|
+
"llm_response": response_json_str,
|
|
118
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
119
|
+
"timestamp": time.time()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
cache_document = LangChainDocument(
|
|
123
|
+
page_content=question,
|
|
124
|
+
metadata=cache_metadata
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
stmt = select(CacheVDBResource).where(
|
|
128
|
+
CacheVDBResource.user_id == user_id
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
result = await db.execute(stmt)
|
|
132
|
+
cache_res = result.scalar_one_or_none()
|
|
133
|
+
|
|
134
|
+
if cache_res is None:
|
|
135
|
+
raise RuntimeError(
|
|
136
|
+
f"Cache VDB resource record not found for user {user_id}"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if not cache_res.vdb_path:
|
|
140
|
+
raise RuntimeError(
|
|
141
|
+
f"Cache VDB path not found for user {user_id}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
user_cache_vdb_path = Path(cache_res.vdb_path)
|
|
145
|
+
|
|
146
|
+
user_cache_vdb = await asyncio.to_thread(
|
|
147
|
+
lambda: Chroma(
|
|
148
|
+
collection_name=f"question_cache_{user_id}",
|
|
149
|
+
embedding_function=embedding_model,
|
|
150
|
+
persist_directory=str(user_cache_vdb_path),
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
await asyncio.to_thread(
|
|
155
|
+
user_cache_vdb.add_documents,
|
|
156
|
+
documents=[cache_document]
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
return cache_metadata["created_at"]
|
|
160
|
+
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
print(f"AI_SERVICE_FAILED, ---WARNING--- | user_id: {user_id} and error: {str(exc)}")
|
|
163
|
+
raise
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# File: portable_cache_schemas/portable_cache_dbConf.py
|
|
2
|
+
import sqlite3
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
|
5
|
+
from sqlalchemy import event
|
|
6
|
+
from portable_cache_schemas.portable_cache_dbBase import Base
|
|
7
|
+
from portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DatabaseManager:
|
|
11
|
+
"""
|
|
12
|
+
Professional Singleton Manager to handle dynamic engine creation
|
|
13
|
+
without module-level 'None' globals or manual boot order guessing.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.celery_engine = None
|
|
17
|
+
self.norma_engine = None
|
|
18
|
+
self.CelerySessionLocal = None
|
|
19
|
+
self.AsyncSessionLocal = None
|
|
20
|
+
self._initialized = False
|
|
21
|
+
|
|
22
|
+
def initialize(self, db_path: str | Path):
|
|
23
|
+
"""Explicitly initializes the engines and session makers."""
|
|
24
|
+
if self._initialized:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
resolved_path = Path(db_path)
|
|
28
|
+
resolved_path.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
DATABASE_URL = f"sqlite+aiosqlite:///{resolved_path}"
|
|
30
|
+
|
|
31
|
+
self.celery_engine = create_async_engine(
|
|
32
|
+
DATABASE_URL,
|
|
33
|
+
connect_args={"timeout": 30},
|
|
34
|
+
)
|
|
35
|
+
self.norma_engine = create_async_engine(
|
|
36
|
+
DATABASE_URL,
|
|
37
|
+
pool_size=20,
|
|
38
|
+
max_overflow=10,
|
|
39
|
+
pool_timeout=30,
|
|
40
|
+
pool_recycle=3600
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.CelerySessionLocal = async_sessionmaker(
|
|
44
|
+
bind=self.celery_engine,
|
|
45
|
+
class_=AsyncSession,
|
|
46
|
+
autoflush=False,
|
|
47
|
+
expire_on_commit=False,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self.AsyncSessionLocal = async_sessionmaker(
|
|
51
|
+
bind=self.norma_engine,
|
|
52
|
+
class_=AsyncSession,
|
|
53
|
+
autoflush=False,
|
|
54
|
+
expire_on_commit=False,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@event.listens_for(self.celery_engine.sync_engine, "connect")
|
|
58
|
+
#"connect" is a SQLAlchemy lifecycle event. It tells SQLAlchemy: "Fire this function the exact millisecond a brand new raw
|
|
59
|
+
#connection to the SQLite database is successfully opened."
|
|
60
|
+
#now sqlite3 is syncro to rlly catch the new connection we need to tap into sync_engine!
|
|
61
|
+
def set_sqlite_pragma(dbapi_connection, connection_record):
|
|
62
|
+
#raw database connection -> dbapi_connection
|
|
63
|
+
#connection_record -> its detail
|
|
64
|
+
cursor = dbapi_connection.cursor()
|
|
65
|
+
cursor.execute("PRAGMA foreign_keys=ON")
|
|
66
|
+
cursor.close()
|
|
67
|
+
#no return was intentional
|
|
68
|
+
|
|
69
|
+
self._initialized = True #made ture for said obj
|
|
70
|
+
|
|
71
|
+
def _auto_bootstrap_if_needed(self):
|
|
72
|
+
"""Self-heals and auto-initializes if a worker or process calls it blindly."""
|
|
73
|
+
if self._initialized:
|
|
74
|
+
return #now we return not above here
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
settings = get_settings()
|
|
78
|
+
registry_db = getattr(settings, "portable_cache_registry_db", None)
|
|
79
|
+
if registry_db and Path(registry_db).exists():
|
|
80
|
+
conn = sqlite3.connect(registry_db)
|
|
81
|
+
cursor = conn.cursor()
|
|
82
|
+
cursor.execute("SELECT db_path FROM paths WHERE id = 1")
|
|
83
|
+
row = cursor.fetchone()
|
|
84
|
+
conn.close()
|
|
85
|
+
|
|
86
|
+
if row and row[0]:
|
|
87
|
+
self.initialize(row[0])
|
|
88
|
+
return
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
raise RuntimeError(
|
|
93
|
+
"Database not initialized! Call init_cache_database(db_path) explicitly "
|
|
94
|
+
"or ensure the registry database is seeded."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
#oh ok ig ill tell u, @property is like typedeff of c++ on drungs asside form being a rename its capable of running stuff as u can see
|
|
98
|
+
#when i need CelerySessionLocal instead of me doing: async with db_manager.CelerySessionLocal as db: i can simaplly async with db_manager.async_session() as db:
|
|
99
|
+
@property
|
|
100
|
+
def celery_session(self):
|
|
101
|
+
self._auto_bootstrap_if_needed()
|
|
102
|
+
return self.CelerySessionLocal
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def async_session(self):
|
|
106
|
+
self._auto_bootstrap_if_needed()
|
|
107
|
+
return self.AsyncSessionLocal
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Instantiate a single global manager for the application lifecycle
|
|
111
|
+
db_manager = DatabaseManager()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def init_cache_database(db_path: str | Path):
|
|
115
|
+
"""Initializes engines and session makers using the path provided by the user's system startup."""
|
|
116
|
+
db_manager.initialize(db_path)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def init_db_tables():
|
|
120
|
+
if db_manager.celery_engine is None: #u may ask this wouldnt happen tho?
|
|
121
|
+
db_manager._auto_bootstrap_if_needed() #and if it did inside it we are accessing settings() which isnt created on 1st run
|
|
122
|
+
#ur correct! this is here for nth run, where env is alredy created so settings() would exist dw! for cold start!
|
|
123
|
+
|
|
124
|
+
async with db_manager.celery_engine.begin() as conn:
|
|
125
|
+
await conn.run_sync(Base.metadata.create_all)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def get_db():
|
|
129
|
+
"""Dependency for yielding database sessions safely with automatic lazy loading."""
|
|
130
|
+
SessionMaker = db_manager.async_session
|
|
131
|
+
async with SessionMaker() as session:
|
|
132
|
+
yield session
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# PEP 562 Module-Level Dynamic Attributes for legacy code/imports compatibility
|
|
136
|
+
def __getattr__(name):
|
|
137
|
+
if name == "CelerySessionLocal":
|
|
138
|
+
return db_manager.celery_session
|
|
139
|
+
if name == "AsyncSessionLocal":
|
|
140
|
+
return db_manager.async_session
|
|
141
|
+
if name == "celery_engine":
|
|
142
|
+
db_manager._auto_bootstrap_if_needed()
|
|
143
|
+
return db_manager.celery_engine
|
|
144
|
+
if name == "norma_engine":
|
|
145
|
+
db_manager._auto_bootstrap_if_needed()
|
|
146
|
+
return db_manager.norma_engine
|
|
147
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# # File: portable_cache_utils/portable_cache_embedding_model.py
|
|
2
|
+
from langchain_huggingface import HuggingFaceEmbeddings
|
|
3
|
+
from portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
|
|
4
|
+
|
|
5
|
+
embedding_model = HuggingFaceEmbeddings(
|
|
6
|
+
model_name=get_settings().portable_cache_cache_proj_embedding_model
|
|
7
|
+
)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# File: portable_cache_utils/protable_cache_DynamicEnv_maker.py
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
6
|
+
from portable_cache_dbSchema import Paths
|
|
7
|
+
from sqlalchemy import create_engine, select
|
|
8
|
+
from pydantic import model_validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_INTERNAL_ENV_FILE = (
|
|
13
|
+
Path.cwd()
|
|
14
|
+
/ ".portable_cache_internal"
|
|
15
|
+
/ ".env_protable_cache"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
class Settings(BaseSettings):
|
|
19
|
+
portable_cache_embedding_model: str = "all-MiniLM-L6-v2"
|
|
20
|
+
portable_cache_chroma_db_dir: str = "./chroma_cache_storage"
|
|
21
|
+
portable_cache_cohere_rerank_model: str = "rerank-english-v3.0"
|
|
22
|
+
portable_cache_cohere_api_key: Optional[str] = None
|
|
23
|
+
portable_cache_redis_url: str = "redis://localhost:6379/0"
|
|
24
|
+
portable_cache_registry_db: Optional[str] = None
|
|
25
|
+
portable_cache_cache_proj_embedding_model: str = "sentence-transformers/all-MiniLM-L6-v2"
|
|
26
|
+
|
|
27
|
+
@model_validator(mode="after")
|
|
28
|
+
def assemble_registry_path(self) -> "Settings":
|
|
29
|
+
if not self.portable_cache_registry_db:
|
|
30
|
+
self.portable_cache_registry_db = str(
|
|
31
|
+
Path(self.portable_cache_chroma_db_dir) / "registry.db"
|
|
32
|
+
)
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
model_config = SettingsConfigDict(
|
|
36
|
+
env_file=_INTERNAL_ENV_FILE if _INTERNAL_ENV_FILE.exists() else (
|
|
37
|
+
Path(__file__).resolve().parent.parent
|
|
38
|
+
/ ".portable_cache_internal"
|
|
39
|
+
/ ".env_protable_cache"
|
|
40
|
+
),
|
|
41
|
+
env_file_encoding="utf-8",
|
|
42
|
+
extra="ignore"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def get_settings() -> Settings:
|
|
46
|
+
"""Always returns a fresh Settings instance loaded from current environment / file."""
|
|
47
|
+
return Settings()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def system_key(redis_url: str, cohere_api_key: str, chroma_db_dir: str = "./chroma_cache_storage", local_cache_dir: Path = None, portable_cache_registry_db: Path = None) -> dict[str, Any]:
|
|
51
|
+
if not redis_url or not cohere_api_key:
|
|
52
|
+
raise ValueError("'redis_url' and 'cohere_api_key' are strictly required to initialize the cache system.")
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
resolved_chroma = str(Path(chroma_db_dir).resolve())
|
|
56
|
+
|
|
57
|
+
if local_cache_dir:
|
|
58
|
+
cache_dir = Path(local_cache_dir)
|
|
59
|
+
else:
|
|
60
|
+
cache_dir = (Path.cwd() / ".portable_cache_internal")
|
|
61
|
+
|
|
62
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
if portable_cache_registry_db:
|
|
65
|
+
db_file = portable_cache_registry_db.resolve()
|
|
66
|
+
else:
|
|
67
|
+
db_file = cache_dir / "registry.db"
|
|
68
|
+
db_file.parent.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
resolved_db_path = str(db_file)
|
|
71
|
+
env_file = cache_dir / ".env_protable_cache"
|
|
72
|
+
|
|
73
|
+
env_lines = []
|
|
74
|
+
if env_file.exists():
|
|
75
|
+
env_lines = env_file.read_text().splitlines()
|
|
76
|
+
|
|
77
|
+
configs = {
|
|
78
|
+
"PORTABLE_CACHE_REDIS_URL": redis_url,
|
|
79
|
+
"PORTABLE_CACHE_COHERE_API_KEY": cohere_api_key,
|
|
80
|
+
"PORTABLE_CACHE_CHROMA_DB_DIR": resolved_chroma,
|
|
81
|
+
"PORTABLE_CACHE_REGISTRY_DB": resolved_db_path
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for key, val in configs.items():
|
|
85
|
+
os.environ[key] = val
|
|
86
|
+
updated = False
|
|
87
|
+
for i, line in enumerate(env_lines):
|
|
88
|
+
if line.startswith(f"{key}="):
|
|
89
|
+
env_lines[i] = f"{key}={val}"
|
|
90
|
+
updated = True
|
|
91
|
+
break
|
|
92
|
+
if not updated:
|
|
93
|
+
env_lines.append(f"{key}={val}")
|
|
94
|
+
|
|
95
|
+
env_file.write_text("\n".join(env_lines) + "\n")
|
|
96
|
+
|
|
97
|
+
sync_engine = create_engine(f"sqlite:///{db_file}")
|
|
98
|
+
from sqlalchemy.orm import sessionmaker
|
|
99
|
+
SessionLocal = sessionmaker(bind=sync_engine)
|
|
100
|
+
|
|
101
|
+
with SessionLocal() as session:
|
|
102
|
+
path_record = session.query(Paths).filter_by(id=1).first()
|
|
103
|
+
if path_record:
|
|
104
|
+
path_record.portable_env_path = str(env_file.resolve())
|
|
105
|
+
path_record.chroma_db_dir = resolved_chroma
|
|
106
|
+
path_record.redis_url = redis_url
|
|
107
|
+
path_record.db_path = resolved_db_path
|
|
108
|
+
path_record.cohere_api_key = cohere_api_key
|
|
109
|
+
else:
|
|
110
|
+
path_record = Paths(
|
|
111
|
+
id=1,
|
|
112
|
+
portable_env_path=str(env_file.resolve()),
|
|
113
|
+
chroma_db_dir=resolved_chroma,
|
|
114
|
+
redis_url=redis_url,
|
|
115
|
+
db_path=resolved_db_path,
|
|
116
|
+
cohere_api_key=cohere_api_key
|
|
117
|
+
)
|
|
118
|
+
session.add(path_record)
|
|
119
|
+
session.commit()
|
|
120
|
+
|
|
121
|
+
print("Portable Cache initialized, saved to .env, and recorded in registry Paths table successfully!")
|
|
122
|
+
return {"success": True, "configured": list(configs.keys())}
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"Error initializing Portable Cache configuration: {str(e)}")
|
|
126
|
+
return {"success": False, "error": str(e), "configured": []}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def get_stored_paths() -> dict:
|
|
130
|
+
"""Instantly pulls absolute paths and credentials from SQLite,
|
|
131
|
+
immune to working directory changes.
|
|
132
|
+
"""
|
|
133
|
+
from portable_cache_schemas.portable_cache_dbConf import db_manager
|
|
134
|
+
|
|
135
|
+
async with db_manager.async_session() as session:
|
|
136
|
+
result = await session.execute(
|
|
137
|
+
select(Paths).filter_by(id=1)
|
|
138
|
+
)
|
|
139
|
+
record = result.scalars().first()
|
|
140
|
+
|
|
141
|
+
if not record:
|
|
142
|
+
raise RuntimeError(
|
|
143
|
+
"Portable Cache not initialized! Run system_key() first."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
"env_path": record.portable_env_path,
|
|
148
|
+
"chroma_dir": record.chroma_db_dir,
|
|
149
|
+
"redis_url": record.redis_url,
|
|
150
|
+
"db_path": record.db_path,
|
|
151
|
+
"cohere_api_key": record.cohere_api_key,
|
|
152
|
+
}
|