TriCacheLLM-MMA 0.1.0__tar.gz → 0.1.2__tar.gz

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 (24) hide show
  1. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/PKG-INFO +3 -1
  2. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/README.md +2 -0
  3. tricachellm_mma-0.1.2/TriCacheLLM_MMA/__init__.py +16 -0
  4. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_Ai/portable_cache_rerankAi.py +2 -2
  5. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_bgWorkers/portable_cache_celery_conf.py +2 -2
  6. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_bgWorkers/portable_cache_workers.py +7 -7
  7. tricachellm_mma-0.1.2/TriCacheLLM_MMA/portable_cache_dbSchema.py +54 -0
  8. tricachellm_mma-0.1.2/TriCacheLLM_MMA/portable_cache_main.py +597 -0
  9. tricachellm_mma-0.1.2/TriCacheLLM_MMA/portable_cache_redis.py +65 -0
  10. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_schemas/portable_cache_dbConf.py +2 -2
  11. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_utils/portable_cache_embedding_model.py +1 -1
  12. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_utils/protable_cache_DynamicEnv_maker.py +1 -1
  13. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/TriCacheLLM_MMA.egg-info/PKG-INFO +3 -1
  14. tricachellm_mma-0.1.2/TriCacheLLM_MMA.egg-info/SOURCES.txt +20 -0
  15. tricachellm_mma-0.1.2/TriCacheLLM_MMA.egg-info/top_level.txt +1 -0
  16. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/pyproject.toml +3 -3
  17. tricachellm_mma-0.1.0/TriCacheLLM_MMA.egg-info/SOURCES.txt +0 -16
  18. tricachellm_mma-0.1.0/TriCacheLLM_MMA.egg-info/top_level.txt +0 -4
  19. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/LICENSE +0 -0
  20. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_schemas/portable_cache_dbBase.py +0 -0
  21. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2/TriCacheLLM_MMA}/portable_cache_schemas/portable_cache_schemas.py +0 -0
  22. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/TriCacheLLM_MMA.egg-info/dependency_links.txt +0 -0
  23. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/TriCacheLLM_MMA.egg-info/requires.txt +0 -0
  24. {tricachellm_mma-0.1.0 → tricachellm_mma-0.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TriCacheLLM_MMA
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: A robust three-tier LLM-aware caching SDK using Redis, ChromaDB, and automatic SQLite bootstrap.
5
5
  Author-email: Mohib Ashfaq Butt <inboxmohib@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/mohib-ash/TriCacheLLM_MMA
@@ -421,6 +421,8 @@ Or install the development version directly from the repository:
421
421
  pip install .
422
422
  ```
423
423
 
424
+ PyPI link: https://pypi.org/project/TriCacheLLM-MMA/0.1.0/
425
+
424
426
  ---
425
427
 
426
428
  # Redis
@@ -380,6 +380,8 @@ Or install the development version directly from the repository:
380
380
  pip install .
381
381
  ```
382
382
 
383
+ PyPI link: https://pypi.org/project/TriCacheLLM-MMA/0.1.0/
384
+
383
385
  ---
384
386
 
385
387
  # Redis
@@ -0,0 +1,16 @@
1
+ from .portable_cache_main import (
2
+ create_cache_system,
3
+ populate_cache,
4
+ check_cache,
5
+ close_cache_system,
6
+ check_tenant_creation_status
7
+ )
8
+
9
+
10
+ __all__ = [
11
+ "create_cache_system",
12
+ "populate_cache",
13
+ "check_cache",
14
+ "close_cache_system",
15
+ "check_tenant_creation_status",
16
+ ]
@@ -5,8 +5,8 @@ from pathlib import Path
5
5
  from sqlalchemy import create_engine
6
6
  from sqlalchemy.orm import sessionmaker
7
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
8
+ from ..portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
9
+ from ..portable_cache_dbSchema import Paths
10
10
 
11
11
  class CohereManager:
12
12
  """Encapsulates the Cohere client to prevent import-time crashes."""
@@ -3,8 +3,8 @@ from pathlib import Path
3
3
  from celery import Celery
4
4
  from sqlalchemy import create_engine
5
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
6
+ from ..portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
7
+ from ..portable_cache_dbSchema import Paths
8
8
 
9
9
  def get_celery_redis_url() -> str:
10
10
  """Sneaks a peek into the SQLite registry DB to grab the true source-of-truth redis_url for Celery."""
@@ -2,18 +2,18 @@
2
2
  import asyncio
3
3
  from pathlib import Path
4
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
5
+ from ..portable_cache_bgWorkers.portable_cache_celery_conf import celery_app
6
+ from ..portable_cache_schemas.portable_cache_dbConf import db_manager
7
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
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
11
  from langchain_core.documents import Document as LangChainDocument
12
12
  from sqlalchemy import select
13
- from portable_cache_utils.portable_cache_embedding_model import embedding_model
13
+ from ..portable_cache_utils.portable_cache_embedding_model import embedding_model
14
14
  from langchain_chroma import Chroma
15
15
  from datetime import datetime, timezone
16
- from portable_cache_dbSchema import Paths
16
+ from ..portable_cache_dbSchema import Paths
17
17
  from sqlalchemy import text
18
18
  import time
19
19
 
@@ -0,0 +1,54 @@
1
+ # File: portable_cache_dbSchema.py
2
+ from sqlalchemy import Column, Integer, DateTime, Boolean, String, Text, ForeignKey, Enum as SQLEnum, text
3
+ from sqlalchemy.sql import func
4
+ from sqlalchemy.orm import relationship
5
+ from .portable_cache_schemas.portable_cache_dbBase import Base
6
+ from .portable_cache_schemas.portable_cache_schemas import CacheVDBStatus
7
+
8
+ class User(Base):
9
+ __tablename__ = "users"
10
+
11
+ user_id = Column(Integer, primary_key=True, autoincrement=True)
12
+ created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
13
+ is_banned = Column(Boolean, nullable=False, server_default=text("0"))
14
+
15
+ cache_vdb_resource = relationship("CacheVDBResource", back_populates="user", uselist=False, cascade="all, delete-orphan")
16
+
17
+
18
+ class CacheVDBResource(Base):
19
+ __tablename__ = "cache_vdb_resources"
20
+
21
+ cache_vdb_id = Column(Integer, primary_key=True, autoincrement=True)
22
+ user_id = Column(Integer, ForeignKey("users.user_id", ondelete="CASCADE"), nullable=False, unique=True, index=True)
23
+
24
+ status = Column(SQLEnum(CacheVDBStatus), default=CacheVDBStatus.PENDING, nullable=False, index=True)
25
+ version = Column(Integer, default=0, server_default="0", nullable=False)
26
+
27
+ vdb_path = Column(String(512), nullable=True)
28
+ failure_reason = Column(Text, nullable=True)
29
+
30
+ created_at = Column(
31
+ DateTime(timezone=True),
32
+ server_default=func.now(),
33
+ nullable=False,
34
+ )
35
+
36
+ updated_at = Column(
37
+ DateTime(timezone=True),
38
+ server_default=func.now(),
39
+ onupdate=func.now(),
40
+ nullable=False,
41
+ )
42
+
43
+ user = relationship("User", back_populates="cache_vdb_resource")
44
+
45
+
46
+ class Paths(Base):
47
+ __tablename__ = "paths"
48
+
49
+ id = Column(Integer, primary_key=True, default=1)
50
+ portable_env_path = Column(String(512), nullable=False)
51
+ chroma_db_dir = Column(String(512), nullable=False)
52
+ redis_url = Column(String(512), nullable=False)
53
+ db_path = Column(String(512), nullable=False)
54
+ cohere_api_key = Column(String(512), nullable=False)
@@ -0,0 +1,597 @@
1
+ # File: portable_cache_main.py
2
+ import asyncio
3
+ import hashlib
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from .portable_cache_schemas.portable_cache_dbConf import init_cache_database, init_db_tables, db_manager
8
+ from .portable_cache_schemas.portable_cache_schemas import CacheVDBStatus
9
+ from .portable_cache_dbSchema import CacheVDBResource
10
+ from sqlalchemy import select
11
+ from .portable_cache_bgWorkers.portable_cache_workers import create_cache_vdb_worker, push_responce_in_cache_worker
12
+ from .portable_cache_utils.portable_cache_embedding_model import embedding_model
13
+ from redis.asyncio import Redis
14
+ from redis.commands.search.field import TagField, VectorField
15
+ from redis.commands.search.index_definition import (
16
+ IndexDefinition,
17
+ IndexType,
18
+ )
19
+ from redis.commands.search.query import Query
20
+ from redis.exceptions import ResponseError
21
+ from pydantic import BaseModel
22
+ import numpy as np
23
+ from langchain_chroma import Chroma
24
+ from langchain_core.documents import Document as LangChainDocument
25
+ import random
26
+ from .portable_cache_Ai.portable_cache_rerankAi import portable_cache_cohere_rerank
27
+ from typing import Optional
28
+ from .portable_cache_redis import get_redis
29
+ from .portable_cache_redis import redis_manager
30
+ from .portable_cache_utils.protable_cache_DynamicEnv_maker import system_key
31
+ from .portable_cache_dbSchema import Paths
32
+
33
+ def create_vector_index_schema(dim: int, distance_metric: str = "COSINE", m: int = 16, ef_construction: int = 200, ef_runtime: int = 10) -> list:
34
+ return [
35
+ VectorField(
36
+ "vector",
37
+ "HNSW",
38
+ {
39
+ "TYPE": "FLOAT32",
40
+ "DIM": dim,
41
+ "DISTANCE_METRIC": distance_metric,
42
+ "M": m,
43
+ "EF_CONSTRUCTION": ef_construction,
44
+ "EF_RUNTIME": ef_runtime
45
+ }
46
+ ),
47
+ TagField("scope"),
48
+ ]
49
+
50
+ async def generate_embedding(text: str) -> list[float]:
51
+ return await asyncio.to_thread(embedding_model.embed_query, text)
52
+
53
+ def generate_cache_key(question: str, user_id: Any = 0) -> str:
54
+ normalized_q = question.strip().lower()
55
+ payload = f"{user_id}:{normalized_q}"
56
+ hash_sig = hashlib.sha256(payload.encode("utf-8")).hexdigest()
57
+ return f"ai_hot_cache:{user_id}:{hash_sig}"
58
+
59
+ async def create_cache_vdb_inishiator(user_id, db) -> dict[str, Any] | None:
60
+ stmt = select(CacheVDBResource).where(
61
+ CacheVDBResource.user_id == user_id,
62
+ )
63
+
64
+ result = await db.execute(stmt)
65
+ cache_resource = result.scalar_one_or_none()
66
+
67
+ if cache_resource is None:
68
+ print(f"CACHE_VDB_CREATING_PENDING where user_id: {user_id}")
69
+ cache_resource = CacheVDBResource(
70
+ user_id=user_id,
71
+ status=CacheVDBStatus.PENDING,
72
+ version=0,
73
+ )
74
+ db.add(cache_resource)
75
+ await db.commit()
76
+ await db.refresh(cache_resource)
77
+
78
+ proceed = cache_resource.status in (
79
+ CacheVDBStatus.PENDING,
80
+ CacheVDBStatus.FAILED,
81
+ )
82
+
83
+ if not proceed:
84
+ print(f"CACHE_VDB_STATUS_SKIPPED where user_id: {user_id}")
85
+ return None
86
+
87
+ print(f"CACHE_VDB_LOCKED_PROCESSING where user_id: {user_id}")
88
+ cache_resource.status = CacheVDBStatus.PROCESSING
89
+ await db.commit()
90
+ await db.refresh(cache_resource)
91
+
92
+ print(f"CACHE_VDB_WORKER_FIRED where user_id: {user_id}")
93
+ task_id = create_cache_vdb_worker.delay(user_id=user_id)
94
+
95
+ data: dict = {
96
+ "success": True,
97
+ "task_id": task_id.id if hasattr(task_id, "id") else str(task_id) if task_id else None,
98
+ "status": "PROCESSING"
99
+ }
100
+ return data
101
+
102
+ async def create_redis_vector_index(user_id, redis_client: Redis, dim: int = 384):
103
+ flag_key = f"meta:index_initialized:{user_id}"
104
+ index_name = f"idx:user_cache:{user_id}"
105
+ print(f"REDIS_INDEX_CHECK_STARTED where user_id: {user_id}")
106
+
107
+ if await redis_client.exists(flag_key):
108
+ try:
109
+ await redis_client.ft(index_name).info()
110
+ print(f"REDIS_INDEX_EXISTS where user_id: {user_id}")
111
+ return
112
+ except ResponseError:
113
+ print(f"REDIS_INDEX_FLAG_STALE where user_id: {user_id}")
114
+
115
+
116
+ schema = (
117
+ VectorField(
118
+ "vector",
119
+ "HNSW",
120
+ {
121
+ "TYPE": "FLOAT32",
122
+ "DIM": dim,
123
+ "DISTANCE_METRIC": "COSINE",
124
+ "M": 16,
125
+ "EF_CONSTRUCTION": 200,
126
+ "EF_RUNTIME": 10
127
+ }
128
+ ),
129
+ TagField("scope"),
130
+ )
131
+
132
+ try:
133
+ print(f"REDIS_INDEX_CREATION_STARTED where user_id: {user_id}")
134
+ await redis_client.ft(index_name).create_index(
135
+ schema,
136
+ definition=IndexDefinition(
137
+ prefix=[f"cache:item:{user_id}:"],
138
+ index_type=IndexType.HASH
139
+ )
140
+ )
141
+ print(f"REDIS_INDEX_CREATED_SUCCESS where user_id: {user_id}")
142
+ except ResponseError as e:
143
+ if "Index already exists" not in str(e):
144
+ print(f"REDIS_INDEX_CREATION_ERROR where error: {str(e)} and user_id: {user_id}")
145
+ raise e
146
+ print(f"REDIS_INDEX_ALREADY_EXISTS where user_id: {user_id}")
147
+
148
+ await redis_client.set(flag_key, "1")
149
+ print(f"REDIS_INDEX_INITIALIZATION_COMPLETE where user_id: {user_id}")
150
+
151
+ def portable_cache_normalize_scope(scope: Any = None) -> str:
152
+ if scope is None:
153
+ return "global"
154
+ if isinstance(scope, list):
155
+ return "__".join(sorted(str(s).strip().lower() for s in scope))
156
+ if isinstance(scope, tuple):
157
+ return "__".join(sorted(str(s).strip().lower() for s in scope))
158
+ return str(scope).strip().lower()
159
+
160
+ def escape_redis_tag(value: str) -> str:
161
+ special_chars = r",.<>{}[]\"':;!@#$%^&*()-+=~|"
162
+ return "".join(
163
+ f"\\{char}" if char in special_chars or char == " " else char
164
+ for char in value
165
+ )
166
+
167
+ async def save_to_redis_vector_cache(user_id,
168
+ question: str,
169
+ response_payload: dict | BaseModel | Any,
170
+ redis_client: Redis,
171
+ scope_input: Any = None
172
+ ):
173
+ print(f"REDIS_VECTOR_SAVE_STARTED where user_id: {user_id}")
174
+ try:
175
+ await create_redis_vector_index(user_id=user_id, redis_client=redis_client)
176
+ print(f"REDIS_VECTOR_INDEX_ENSURED where user_id: {user_id}")
177
+
178
+ vector = await generate_embedding(question)
179
+ print(f"REDIS_VECTOR_EMBEDDING_GENERATED where user_id: {user_id}")
180
+ vector_bytes = np.array(vector, dtype=np.float32).tobytes()
181
+
182
+ if isinstance(response_payload, BaseModel):
183
+ payload_data = response_payload.model_dump_json()
184
+ else:
185
+ payload_data = json.dumps(response_payload)
186
+ print(f"REDIS_VECTOR_PAYLOAD_SERIALIZED where user_id: {user_id}")
187
+
188
+ normalized_question = question.strip().lower()
189
+ scope = portable_cache_normalize_scope(scope_input)
190
+
191
+ scope_payload = f"{normalized_question}:{scope}"
192
+ entry_id = hashlib.sha256(scope_payload.encode("utf-8")).hexdigest()
193
+ redis_key = f"cache:item:{user_id}:{entry_id}"
194
+
195
+ await redis_client.hset(
196
+ redis_key,
197
+ mapping={
198
+ "vector": vector_bytes,
199
+ "response_data": payload_data,
200
+ "scope": scope,
201
+ }
202
+ )
203
+ print(f"REDIS_VECTOR_HASH_SAVED where user_id: {user_id}")
204
+
205
+ await asyncio.create_task(redis_client.expire(redis_key, 86400))
206
+ print(f"REDIS_VECTOR_TTL_SET where user_id: {user_id}")
207
+
208
+ except Exception as e:
209
+ print(f"REDIS_VECTOR_SAVE_ERROR where error: {str(e)} and user_id: {user_id}")
210
+ raise
211
+
212
+ async def get_user_cache_vdb(user_id, db) -> Chroma | None:
213
+ print(f"CHECKING_USER_VDB where user_id: {user_id}")
214
+
215
+ stmt = select(CacheVDBResource).where(CacheVDBResource.user_id == user_id)
216
+ result = await db.execute(stmt)
217
+ cache_res = result.scalar_one_or_none()
218
+
219
+ if cache_res is None or cache_res.status != CacheVDBStatus.READY or not cache_res.vdb_path:
220
+ print(f"VDB_NOT_FOUND where user_id: {user_id}")
221
+ return None
222
+
223
+ print(f"VDB_FOUND where user_id: {user_id}")
224
+
225
+ return await asyncio.to_thread(
226
+ lambda: Chroma(
227
+ persist_directory=cache_res.vdb_path,
228
+ collection_name=f"question_cache_{user_id}",
229
+ embedding_function=embedding_model,
230
+ )
231
+ )
232
+
233
+ async def get_cached(question: str, cache_vdb: Chroma, user_id) -> dict[str, Any]:
234
+ print(f"CHROMA_SIMILARITY_SEARCH_STARTED where user_id: {user_id}")
235
+ try:
236
+ fetched_and_scores: list[tuple[LangChainDocument, float]] = await asyncio.to_thread(
237
+ cache_vdb.similarity_search_with_score,
238
+ query=question,
239
+ k=10
240
+ )
241
+ except Exception as exc:
242
+ print(f"CHROMA_SIMILARITY_SEARCH_ERROR where error: {str(exc)} and user_id: {user_id}")
243
+ return {"success": False, "data": None}
244
+
245
+ if not fetched_and_scores:
246
+ print(f"CHROMA_CANDIDATES_NOT_FOUND where user_id: {user_id}")
247
+ return {"success": False, "data": None}
248
+
249
+ documents_for_rerank = [
250
+ LangChainDocument(
251
+ page_content=(
252
+ f"Cached Question:\n{doc.metadata.get('question')}\n\n"
253
+ f"Cached Answer:\n{doc.metadata.get('llm_response')}"
254
+ ),
255
+ metadata=doc.metadata
256
+ )
257
+ for doc, score in fetched_and_scores
258
+ ]
259
+
260
+ print(f"CHROMA_RERANK_STARTED where user_id: {user_id}")
261
+ rerank_response: dict = await portable_cache_cohere_rerank(
262
+ question=question,
263
+ user_id=user_id,
264
+ received_docs=documents_for_rerank,
265
+ top_k=3
266
+ )
267
+
268
+ if not rerank_response["success"]:
269
+ print(f"CHROMA_RERANK_FAILED where user_id: {user_id}")
270
+ return {"success": False, "data": None}
271
+
272
+ best_doc = rerank_response["data"][0]
273
+ best_score = best_doc.metadata.get("rerank_score", 0.0)
274
+
275
+ RELEVANCE_THRESHOLD = 0.85
276
+ DELIBERATE_MISS_RATE = 0.10
277
+
278
+ if best_score < RELEVANCE_THRESHOLD:
279
+ print(f"CHROMA_RELEVANCE_THRESHOLD_FAILED where user_id: {user_id}")
280
+ return {"success": False, "data": None}
281
+
282
+ if random.random() < DELIBERATE_MISS_RATE:
283
+ print(f"CHROMA_DELIBERATE_MISS_TRIGGERED where user_id: {user_id}")
284
+ return {"success": False, "data": None}
285
+
286
+ print(f"CHROMA_CACHE_HIT where user_id: {user_id}")
287
+ raw_response = best_doc.metadata.get("full_llm_response") or best_doc.metadata.get("llm_response")
288
+ parsed_answer = json.loads(raw_response) if isinstance(raw_response, str) else raw_response
289
+
290
+ return {"success": True, "data": parsed_answer}
291
+
292
+ async def get_redis_vector_cached(question: str, user_id, redis_client: Redis, top_k: int = 1, scope: Any = None) -> dict[str, Any]:
293
+ print(f"REDIS_VECTOR_SEARCH_STARTED where user_id: {user_id}")
294
+ try:
295
+ print(f"REDIS_VECTOR_GENERATING_EMBEDDING where user_id: {user_id}")
296
+ question_vector: list[float] = await generate_embedding(question)
297
+
298
+ vector_bytes = np.array(question_vector, dtype=np.float32).tobytes()
299
+ index_name = f"idx:user_cache:{user_id}"
300
+
301
+ normalized_scope: str = portable_cache_normalize_scope(scope)
302
+ print(f"REDIS_VECTOR_SCOPE_NORMALIZED where user_id: {user_id}")
303
+
304
+ escaped_scope = escape_redis_tag(normalized_scope)
305
+ query_str = (
306
+ f"(@scope:{{{escaped_scope}}})"
307
+ f"=>[KNN {top_k} @vector $vec AS vector_score]"
308
+ )
309
+
310
+ query = (
311
+ Query(query_str)
312
+ .return_fields("response_data", "vector_score",)
313
+ .sort_by("vector_score")
314
+ .dialect(2)
315
+ )
316
+
317
+ print(f"REDIS_VECTOR_QUERY_EXECUTING where user_id: {user_id}")
318
+ results = await redis_client.ft(index_name).search(
319
+ query,
320
+ query_params={"vec": vector_bytes}
321
+ )
322
+
323
+ if results and results.docs:
324
+ doc = results.docs[0]
325
+
326
+ score = float(doc.vector_score)
327
+ if score > 0.20:
328
+ # print(f"REDIS_VECTOR_SCORE where user_id: {user_id} "f"score={score}")
329
+ print(f"REDIS_VECTOR_SCORE_MISMATCH where user_id: {user_id}")
330
+ return {"success": False, "data": None}
331
+
332
+
333
+
334
+ print(f"REDIS_VECTOR_HIT_FOUND where user_id: {user_id}")
335
+ return {"success": True, "data": json.loads(doc.response_data)}
336
+
337
+ print(f"REDIS_VECTOR_CACHE_MISS user_id: {user_id}")
338
+ return {"success": False, "data": None}
339
+
340
+ except Exception as e:
341
+ print(f"REDIS_VECTOR_ERROR where error: {str(e)} and user_id: {user_id}")
342
+ return {"success": False, "data": None}
343
+
344
+ async def multi_tier_cache_system(question: str, user_id, normal_redis_instance: Redis, db, cache_key: str, scope: Any = None, top_k: int = 1) -> dict[str, Any]:
345
+ #Tier 1:
346
+ cached_payload = await normal_redis_instance.get(cache_key)
347
+ if cached_payload:
348
+ print(f"CACHE_TIER1_EXACT_HIT where user_id: {user_id}")
349
+ return {"success": True, "data": json.loads(cached_payload)}
350
+
351
+ #Tier 2:
352
+ print(f"CACHE_POLICY_CACHEABLE where user_id: {user_id}")
353
+ redis_vector_result: dict = await get_redis_vector_cached(question=question, user_id=user_id, redis_client=normal_redis_instance, scope=scope, top_k=top_k)
354
+
355
+ if redis_vector_result["success"]:
356
+ print(f"CACHE_TIER2_VECTOR_HIT where user_id: {user_id}")
357
+ asyncio.create_task(
358
+ normal_redis_instance.setex(cache_key, 86400, json.dumps(redis_vector_result["data"]))
359
+ )
360
+ return {"success": True, "data": redis_vector_result["data"]}
361
+
362
+
363
+ #Tier 3:
364
+ cache_vdb: Chroma | None = await get_user_cache_vdb(user_id=user_id, db=db)
365
+ if cache_vdb is not None:
366
+ chroma_result: dict = await get_cached(question=question, cache_vdb=cache_vdb, user_id=user_id)
367
+
368
+ if chroma_result["success"]:
369
+ print(f"CACHE_TIER3_CHROMA_HIT where user_id: {user_id}")
370
+ async def run_both():
371
+ await asyncio.gather(
372
+ normal_redis_instance.setex(cache_key, 86400, json.dumps(chroma_result["data"])),
373
+ save_to_redis_vector_cache(
374
+ user_id=user_id,
375
+ question=question,
376
+ response_payload=chroma_result["data"],
377
+ redis_client=normal_redis_instance,
378
+ scope_input=scope,
379
+ ),
380
+ return_exceptions=True
381
+ )
382
+
383
+ asyncio.create_task(run_both())
384
+ return {"data": chroma_result["data"], "success": True}
385
+ else:
386
+ print(f"CACHE_SYSTEM_MISS_FALLBACK where user_id: {user_id}")
387
+ return {"success": False, "data": None}
388
+ else:
389
+ print(f"CACHE_VDB_NOT_FOUND where user_id: {user_id}")
390
+ return {"success": False, "data": None}
391
+
392
+ async def push_responce_in_cache_inishiator(model_output: Any, question: str, user_id) -> dict:
393
+ model_dict = model_output.model_dump() if hasattr(model_output, "model_dump") else model_output
394
+
395
+ task = push_responce_in_cache_worker.delay(
396
+ user_id=user_id,
397
+ question=question,
398
+ model_output_dict=model_dict,
399
+ )
400
+ return {
401
+ "success": True,
402
+ "task_id": task.id if hasattr(task, "id") else str(task.id),
403
+ "status": "PROCESSING"
404
+ }
405
+
406
+
407
+
408
+
409
+
410
+
411
+ async def create_cache_system(
412
+ redis_url: Optional[str] = None,
413
+ cohere_api_key: Optional[str] = None,
414
+ chroma_db_dir: Optional[str] = None,
415
+ db_path: Optional[str] = None,
416
+ user_id: Any = 0
417
+ ) -> dict[str, bool | str] | None:
418
+
419
+ print(f"CACHE_SYSTEM_INIT_STARTED where user_id: {user_id}")
420
+ try:
421
+ if user_id == 0:
422
+ if not all([
423
+ redis_url,
424
+ cohere_api_key,
425
+ chroma_db_dir,
426
+ db_path,
427
+ ]):
428
+ raise ValueError(
429
+ "System initialization requires redis_url, "
430
+ "cohere_api_key, chroma_db_dir and db_path."
431
+ )
432
+
433
+ #these are to be made for the first time
434
+ db_file = Path(db_path)
435
+ init_cache_database(db_file) #this creates all session mamanger for db_manager object!
436
+ await init_db_tables() #this will stay out regardless in case of server cold restart
437
+
438
+ key_res: dict = system_key(redis_url=redis_url, chroma_db_dir=chroma_db_dir, cohere_api_key=cohere_api_key, portable_cache_registry_db=Path(db_path))
439
+ if not key_res["success"]:
440
+ raise ValueError("Failed to initialize system keys. Verify if the keys are correct.")
441
+
442
+ return #user_id=0 doesnt need its own vdb! if you want to be it user-self be user_id=1 -> single user
443
+ #if you want multi-tanent then keep sending in user_ids lol
444
+
445
+
446
+ async with db_manager.async_session() as db:
447
+ ans: None | dict = await create_cache_vdb_inishiator(user_id=user_id, db=db)
448
+
449
+ if ans is None:
450
+ print(f"CACHE_SYSTEM_INIT_SKIPPED: Already exists or in progress for user_id: {user_id}")
451
+ return {
452
+ "success": False,
453
+ "task_id": None,
454
+ "status_msg": "already created or is being created"
455
+ }
456
+
457
+ if ans.get("success"):
458
+ task_id = ans.get("task_id")
459
+ print(f"CACHE_SYSTEM_INIT_STARTED_ASYNC with task_id: {task_id} for user_id: {user_id}")
460
+ return {
461
+ "success": True,
462
+ "task_id": task_id,
463
+ "status_msg": "Started making"
464
+ }
465
+ else:
466
+ print(f"CACHE_SYSTEM_INIT_FAILED for user_id: {user_id}")
467
+ return {
468
+ "success": False,
469
+ "task_id": None,
470
+ "status_msg": "Initialization failed upstream"
471
+ }
472
+
473
+ except Exception as exc:
474
+ print(f"CACHE_SYSTEM_INIT_ERROR where error: {str(exc)} and user_id: {user_id}")
475
+ return {
476
+ "success": False,
477
+ "task_id": None,
478
+ "status_msg": f"Error: {str(exc)}"
479
+ }
480
+
481
+ async def check_cache(user_input: str, scope: Optional[str] = None, user_id: Any = 0) -> dict | None:
482
+ print(f"CHECKING_MULTI_TIER_CACHE where user_id: {user_id}")
483
+ try:
484
+ async with db_manager.async_session() as db:
485
+ path_record = await db.get(Paths, 1) #get me 1st row as this is consumer only thus singletn row, btw consumer can have n users dw!
486
+ if not path_record or not path_record.redis_url:
487
+ raise ValueError("System registry not initialized! Run create_cache_system first.")
488
+
489
+ redis_url = path_record.redis_url
490
+ cache_key: str = generate_cache_key(question=user_input, user_id=user_id)
491
+
492
+ normal_redis = await get_redis(redis_url=redis_url) #dw this will work as when we import portable_cache_redis.py all loads
493
+ cache_res: dict = await multi_tier_cache_system(
494
+ scope=scope,
495
+ cache_key=cache_key,
496
+ normal_redis_instance=normal_redis,
497
+ question=user_input,
498
+ db=db,
499
+ user_id=user_id
500
+ )
501
+
502
+ if cache_res and cache_res.get("success"):
503
+ return cache_res.get("data")
504
+
505
+ return None
506
+ except Exception as e:
507
+ print(f"Error checking cache for user_id {user_id}: {e}")
508
+ return None
509
+
510
+ async def populate_cache(to_cache_answer, to_cache_question, user_id: Any = 0) -> dict | None:
511
+ print(f"BACKGROUND_CACHE_POPULATION_FIRED where user_id: {user_id}")
512
+ try:
513
+ async with db_manager.async_session() as db:
514
+ cache_vdb: Chroma | None = await get_user_cache_vdb(user_id=user_id, db=db)
515
+ if cache_vdb is not None:
516
+ ans: dict = await push_responce_in_cache_inishiator(model_output=to_cache_answer, user_id=user_id, question=to_cache_question)
517
+ if ans["success"]:
518
+ return ans
519
+ return None
520
+ except Exception as e:
521
+ print(f"Error populating cache for user_id {user_id}: {e}")
522
+ return None
523
+
524
+ async def close_cache_system():
525
+ print("SHUTTING_DOWN_PORTABLE_CACHE: Cleaning up resources...")
526
+
527
+ from portable_cache_schemas.portable_cache_dbConf import db_manager
528
+
529
+ if db_manager.norma_engine is not None:
530
+ try:
531
+ await db_manager.norma_engine.dispose()
532
+ print("Norma database engine disposed successfully.")
533
+ except Exception as e:
534
+ print(f"Error disposing norma_engine: {e}")
535
+
536
+ if db_manager.celery_engine is not None:
537
+ try:
538
+ await db_manager.celery_engine.dispose()
539
+ print("Celery database engine disposed successfully.")
540
+ except Exception as e:
541
+ print(f"Error disposing celery_engine: {e}")
542
+
543
+ try:
544
+ await redis_manager.close_redis_pool()
545
+ print("Redis connection pool disconnected successfully.")
546
+ except Exception as e:
547
+ print(f"Error disconnecting Redis pool: {e}")
548
+
549
+ print("Portable Cache shutdown complete!")
550
+
551
+ def check_tenant_creation_status(task_id, user_id) -> dict:
552
+ task_id: str = str(task_id)
553
+ user_id: int = int(user_id)
554
+
555
+ from celery.result import AsyncResult
556
+ from .portable_cache_bgWorkers.portable_cache_celery_conf import celery_app
557
+ from celery.states import SUCCESS, FAILURE, RETRY
558
+
559
+ async_result = AsyncResult(task_id, app=celery_app)
560
+ state = async_result.state
561
+
562
+ if state == SUCCESS:
563
+ data={
564
+ "status": "completed",
565
+ "task_id": task_id,
566
+ "state": state,
567
+ "failed": False
568
+ }
569
+ return data
570
+
571
+ elif state == FAILURE:
572
+ data={
573
+ "status": "failed",
574
+ "task_id": task_id,
575
+ "state": state,
576
+ "failed": True
577
+ }
578
+ return data
579
+
580
+ elif state == RETRY:
581
+ data={
582
+ "status": "retrying",
583
+ "task_id": task_id,
584
+ "state": state,
585
+ "failed": False
586
+ }
587
+ return data
588
+
589
+ else:
590
+ data={
591
+ "status": "processing",
592
+ "task_id": task_id,
593
+ "state": state,
594
+ "failed": False
595
+ }
596
+ return data
597
+
@@ -0,0 +1,65 @@
1
+ # File: portable_cache_redis.py
2
+ from pathlib import Path
3
+ import redis.asyncio as aioredis
4
+ from typing import Optional
5
+ from .portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
6
+ from .portable_cache_dbSchema import Paths
7
+ from sqlalchemy import create_engine
8
+ from sqlalchemy.orm import sessionmaker
9
+
10
+ class RedisManager:
11
+ """Encapsulates the Redis connection pool without relying on unsafe global state."""
12
+
13
+ def __init__(self):
14
+ self._pool: Optional[aioredis.ConnectionPool] = None
15
+ self._current_url: Optional[str] = None
16
+
17
+ def _get_redis_url_from_registry(self) -> Optional[str]:
18
+ """Sneaks a peek into the SQLite registry DB to grab the true source-of-truth redis_url."""
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.redis_url:
32
+ return path_record.redis_url
33
+ except Exception:
34
+ pass
35
+ return None
36
+
37
+ async def get_client(self, redis_url: Optional[str] = None) -> aioredis.Redis:
38
+ """Lazily initializes and returns a connection-pooled Redis client."""
39
+ settings = get_settings()
40
+
41
+ target_url = redis_url or self._get_redis_url_from_registry() or settings.portable_cache_redis_url
42
+
43
+ if self._pool is None or self._current_url != target_url:
44
+ if self._pool:
45
+ await self._pool.disconnect()
46
+
47
+ self._current_url = target_url
48
+ self._pool = aioredis.ConnectionPool.from_url(
49
+ target_url,
50
+ decode_responses=False #false coz vectors are stored in bytes!
51
+ )
52
+
53
+ return aioredis.Redis(connection_pool=self._pool)
54
+
55
+ async def close_redis_pool(self):
56
+ """Clean shutdown for app teardown/lifespan events."""
57
+ if self._pool:
58
+ await self._pool.disconnect()
59
+ self._pool = None
60
+
61
+ redis_manager = RedisManager()
62
+
63
+ async def get_redis(redis_url: Optional[str] = None) -> aioredis.Redis:
64
+ """Helper function to cleanly fetch the client."""
65
+ return await redis_manager.get_client(redis_url=redis_url)
@@ -3,8 +3,8 @@ import sqlite3
3
3
  from pathlib import Path
4
4
  from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
5
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
6
+ from ..portable_cache_schemas.portable_cache_dbBase import Base
7
+ from ..portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
8
8
 
9
9
 
10
10
  class DatabaseManager:
@@ -1,6 +1,6 @@
1
1
  # # File: portable_cache_utils/portable_cache_embedding_model.py
2
2
  from langchain_huggingface import HuggingFaceEmbeddings
3
- from portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
3
+ from ..portable_cache_utils.protable_cache_DynamicEnv_maker import get_settings
4
4
 
5
5
  embedding_model = HuggingFaceEmbeddings(
6
6
  model_name=get_settings().portable_cache_cache_proj_embedding_model
@@ -3,7 +3,7 @@ import os
3
3
  from pathlib import Path
4
4
  from typing import Any, Optional
5
5
  from pydantic_settings import BaseSettings, SettingsConfigDict
6
- from portable_cache_dbSchema import Paths
6
+ from ..portable_cache_dbSchema import Paths
7
7
  from sqlalchemy import create_engine, select
8
8
  from pydantic import model_validator
9
9
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TriCacheLLM_MMA
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: A robust three-tier LLM-aware caching SDK using Redis, ChromaDB, and automatic SQLite bootstrap.
5
5
  Author-email: Mohib Ashfaq Butt <inboxmohib@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/mohib-ash/TriCacheLLM_MMA
@@ -421,6 +421,8 @@ Or install the development version directly from the repository:
421
421
  pip install .
422
422
  ```
423
423
 
424
+ PyPI link: https://pypi.org/project/TriCacheLLM-MMA/0.1.0/
425
+
424
426
  ---
425
427
 
426
428
  # Redis
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ TriCacheLLM_MMA/__init__.py
5
+ TriCacheLLM_MMA/portable_cache_dbSchema.py
6
+ TriCacheLLM_MMA/portable_cache_main.py
7
+ TriCacheLLM_MMA/portable_cache_redis.py
8
+ TriCacheLLM_MMA.egg-info/PKG-INFO
9
+ TriCacheLLM_MMA.egg-info/SOURCES.txt
10
+ TriCacheLLM_MMA.egg-info/dependency_links.txt
11
+ TriCacheLLM_MMA.egg-info/requires.txt
12
+ TriCacheLLM_MMA.egg-info/top_level.txt
13
+ TriCacheLLM_MMA/portable_cache_Ai/portable_cache_rerankAi.py
14
+ TriCacheLLM_MMA/portable_cache_bgWorkers/portable_cache_celery_conf.py
15
+ TriCacheLLM_MMA/portable_cache_bgWorkers/portable_cache_workers.py
16
+ TriCacheLLM_MMA/portable_cache_schemas/portable_cache_dbBase.py
17
+ TriCacheLLM_MMA/portable_cache_schemas/portable_cache_dbConf.py
18
+ TriCacheLLM_MMA/portable_cache_schemas/portable_cache_schemas.py
19
+ TriCacheLLM_MMA/portable_cache_utils/portable_cache_embedding_model.py
20
+ TriCacheLLM_MMA/portable_cache_utils/protable_cache_DynamicEnv_maker.py
@@ -0,0 +1 @@
1
+ TriCacheLLM_MMA
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "TriCacheLLM_MMA"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "A robust three-tier LLM-aware caching SDK using Redis, ChromaDB, and automatic SQLite bootstrap."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -62,5 +62,5 @@ Homepage = "https://github.com/mohib-ash/TriCacheLLM_MMA"
62
62
  Repository = "https://github.com/mohib-ash/TriCacheLLM_MMA"
63
63
  Issues = "https://github.com/mohib-ash/TriCacheLLM_MMA/issues"
64
64
 
65
- [tool.setuptools.packages.find]
66
- include = ["portable_cache_*"]
65
+ [tool.setuptools]
66
+ packages = ["TriCacheLLM_MMA", "TriCacheLLM_MMA.portable_cache_Ai", "TriCacheLLM_MMA.portable_cache_bgWorkers", "TriCacheLLM_MMA.portable_cache_schemas", "TriCacheLLM_MMA.portable_cache_utils"]
@@ -1,16 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- TriCacheLLM_MMA.egg-info/PKG-INFO
5
- TriCacheLLM_MMA.egg-info/SOURCES.txt
6
- TriCacheLLM_MMA.egg-info/dependency_links.txt
7
- TriCacheLLM_MMA.egg-info/requires.txt
8
- TriCacheLLM_MMA.egg-info/top_level.txt
9
- portable_cache_Ai/portable_cache_rerankAi.py
10
- portable_cache_bgWorkers/portable_cache_celery_conf.py
11
- portable_cache_bgWorkers/portable_cache_workers.py
12
- portable_cache_schemas/portable_cache_dbBase.py
13
- portable_cache_schemas/portable_cache_dbConf.py
14
- portable_cache_schemas/portable_cache_schemas.py
15
- portable_cache_utils/portable_cache_embedding_model.py
16
- portable_cache_utils/protable_cache_DynamicEnv_maker.py
@@ -1,4 +0,0 @@
1
- portable_cache_Ai
2
- portable_cache_bgWorkers
3
- portable_cache_schemas
4
- portable_cache_utils
File without changes