prismrag-patch 0.1.0__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.
- prismrag_patch-0.1.0/PKG-INFO +89 -0
- prismrag_patch-0.1.0/README.md +50 -0
- prismrag_patch-0.1.0/prismrag_patch/__init__.py +12 -0
- prismrag_patch-0.1.0/prismrag_patch/adapters/__init__.py +13 -0
- prismrag_patch-0.1.0/prismrag_patch/adapters/chroma.py +111 -0
- prismrag_patch-0.1.0/prismrag_patch/adapters/pgvector.py +133 -0
- prismrag_patch-0.1.0/prismrag_patch/adapters/pinecone.py +120 -0
- prismrag_patch-0.1.0/prismrag_patch/adapters/weaviate.py +111 -0
- prismrag_patch-0.1.0/prismrag_patch/core.py +150 -0
- prismrag_patch-0.1.0/prismrag_patch/license.py +117 -0
- prismrag_patch-0.1.0/prismrag_patch.egg-info/PKG-INFO +89 -0
- prismrag_patch-0.1.0/prismrag_patch.egg-info/SOURCES.txt +15 -0
- prismrag_patch-0.1.0/prismrag_patch.egg-info/dependency_links.txt +1 -0
- prismrag_patch-0.1.0/prismrag_patch.egg-info/requires.txt +22 -0
- prismrag_patch-0.1.0/prismrag_patch.egg-info/top_level.txt +1 -0
- prismrag_patch-0.1.0/pyproject.toml +49 -0
- prismrag_patch-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prismrag-patch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drop-in hallucination-resistant retrieval for pgvector, ChromaDB, Pinecone, and Weaviate
|
|
5
|
+
Author-email: Insight IT Solutions <sales@prismrag.insightits.com>
|
|
6
|
+
License: Commercial
|
|
7
|
+
Project-URL: Homepage, https://prismrag.insightits.com/prismrag-lib.html
|
|
8
|
+
Project-URL: Repository, https://github.com/aminparva84/InsightPrismRAG
|
|
9
|
+
Project-URL: Bug Tracker, https://prismrag.insightits.com/support
|
|
10
|
+
Keywords: rag,vector-database,hallucination,pgvector,chromadb,pinecone,weaviate,llm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: requests>=2.28
|
|
23
|
+
Requires-Dist: numpy>=1.24
|
|
24
|
+
Provides-Extra: pgvector
|
|
25
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "pgvector"
|
|
26
|
+
Requires-Dist: pgvector>=0.2; extra == "pgvector"
|
|
27
|
+
Provides-Extra: chroma
|
|
28
|
+
Requires-Dist: chromadb>=0.4; extra == "chroma"
|
|
29
|
+
Provides-Extra: pinecone
|
|
30
|
+
Requires-Dist: pinecone-client>=3.0; extra == "pinecone"
|
|
31
|
+
Provides-Extra: weaviate
|
|
32
|
+
Requires-Dist: weaviate-client>=4.0; extra == "weaviate"
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "all"
|
|
35
|
+
Requires-Dist: pgvector>=0.2; extra == "all"
|
|
36
|
+
Requires-Dist: chromadb>=0.4; extra == "all"
|
|
37
|
+
Requires-Dist: pinecone-client>=3.0; extra == "all"
|
|
38
|
+
Requires-Dist: weaviate-client>=4.0; extra == "all"
|
|
39
|
+
|
|
40
|
+
# prismrag-patch
|
|
41
|
+
|
|
42
|
+
**Drop-in hallucination-resistant retrieval for your own vector database.**
|
|
43
|
+
|
|
44
|
+
PrismRAG Patch wraps pgvector, ChromaDB, Pinecone, or Weaviate with PrismRAG's
|
|
45
|
+
Tier-1 re-mapping technique — deterministic category projection that grounds every
|
|
46
|
+
chunk in your verified rules before it ever reaches the LLM.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from prismrag_patch import PrismRAGPatch
|
|
50
|
+
from prismrag_patch.adapters.pgvector import PgvectorAdapter
|
|
51
|
+
|
|
52
|
+
mapping = {
|
|
53
|
+
"categories": [
|
|
54
|
+
{"slug": "risk", "label": "Risk & Compliance"},
|
|
55
|
+
{"slug": "growth", "label": "Growth"},
|
|
56
|
+
],
|
|
57
|
+
"rules": [
|
|
58
|
+
{"word": "volatility", "category_slug": "risk", "weight": 1.0},
|
|
59
|
+
{"word": "revenue", "category_slug": "growth", "weight": 1.0},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
patch = PrismRAGPatch(license_key="prlib_…", mapping=mapping)
|
|
64
|
+
adapter = PgvectorAdapter(patch, connection=your_psycopg2_conn)
|
|
65
|
+
|
|
66
|
+
# Insert — re-mapped before storage
|
|
67
|
+
adapter.insert(text=doc, vector=embed(doc))
|
|
68
|
+
|
|
69
|
+
# Search — query re-mapped identically, no hallucination path
|
|
70
|
+
results = adapter.search("what is our risk exposure?", query_vector=q_vec)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install prismrag-patch # core only
|
|
77
|
+
pip install "prismrag-patch[pgvector]" # + pgvector support
|
|
78
|
+
pip install "prismrag-patch[chroma]" # + ChromaDB support
|
|
79
|
+
pip install "prismrag-patch[pinecone]" # + Pinecone support
|
|
80
|
+
pip install "prismrag-patch[weaviate]" # + Weaviate support
|
|
81
|
+
pip install "prismrag-patch[all]" # all adapters
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Commercial license required. Get yours at
|
|
87
|
+
[prismrag.insightits.com/prismrag-lib.html](https://prismrag.insightits.com/prismrag-lib.html).
|
|
88
|
+
|
|
89
|
+
© 2026 Insight IT Solutions
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# prismrag-patch
|
|
2
|
+
|
|
3
|
+
**Drop-in hallucination-resistant retrieval for your own vector database.**
|
|
4
|
+
|
|
5
|
+
PrismRAG Patch wraps pgvector, ChromaDB, Pinecone, or Weaviate with PrismRAG's
|
|
6
|
+
Tier-1 re-mapping technique — deterministic category projection that grounds every
|
|
7
|
+
chunk in your verified rules before it ever reaches the LLM.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from prismrag_patch import PrismRAGPatch
|
|
11
|
+
from prismrag_patch.adapters.pgvector import PgvectorAdapter
|
|
12
|
+
|
|
13
|
+
mapping = {
|
|
14
|
+
"categories": [
|
|
15
|
+
{"slug": "risk", "label": "Risk & Compliance"},
|
|
16
|
+
{"slug": "growth", "label": "Growth"},
|
|
17
|
+
],
|
|
18
|
+
"rules": [
|
|
19
|
+
{"word": "volatility", "category_slug": "risk", "weight": 1.0},
|
|
20
|
+
{"word": "revenue", "category_slug": "growth", "weight": 1.0},
|
|
21
|
+
],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
patch = PrismRAGPatch(license_key="prlib_…", mapping=mapping)
|
|
25
|
+
adapter = PgvectorAdapter(patch, connection=your_psycopg2_conn)
|
|
26
|
+
|
|
27
|
+
# Insert — re-mapped before storage
|
|
28
|
+
adapter.insert(text=doc, vector=embed(doc))
|
|
29
|
+
|
|
30
|
+
# Search — query re-mapped identically, no hallucination path
|
|
31
|
+
results = adapter.search("what is our risk exposure?", query_vector=q_vec)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install prismrag-patch # core only
|
|
38
|
+
pip install "prismrag-patch[pgvector]" # + pgvector support
|
|
39
|
+
pip install "prismrag-patch[chroma]" # + ChromaDB support
|
|
40
|
+
pip install "prismrag-patch[pinecone]" # + Pinecone support
|
|
41
|
+
pip install "prismrag-patch[weaviate]" # + Weaviate support
|
|
42
|
+
pip install "prismrag-patch[all]" # all adapters
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
Commercial license required. Get yours at
|
|
48
|
+
[prismrag.insightits.com/prismrag-lib.html](https://prismrag.insightits.com/prismrag-lib.html).
|
|
49
|
+
|
|
50
|
+
© 2026 Insight IT Solutions
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
prismrag-patch — hallucination-resistant retrieval for your vector database.
|
|
3
|
+
|
|
4
|
+
Quick start:
|
|
5
|
+
from prismrag_patch import PrismRAGPatch
|
|
6
|
+
patch = PrismRAGPatch(license_key="prlib_…", mapping=your_mapping)
|
|
7
|
+
"""
|
|
8
|
+
from prismrag_patch.core import PrismRAGPatch
|
|
9
|
+
from prismrag_patch.license import LicenseError, validate_license
|
|
10
|
+
|
|
11
|
+
__all__ = ["PrismRAGPatch", "LicenseError", "validate_license"]
|
|
12
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Adapter registry for prismrag-patch.
|
|
3
|
+
|
|
4
|
+
Each adapter wraps a specific vector database and applies PrismRAG
|
|
5
|
+
Tier-1 re-mapping on insert and search automatically.
|
|
6
|
+
|
|
7
|
+
Available adapters
|
|
8
|
+
------------------
|
|
9
|
+
- pgvector : prismrag_patch.adapters.pgvector.PgvectorAdapter
|
|
10
|
+
- chroma : prismrag_patch.adapters.chroma.ChromaAdapter
|
|
11
|
+
- pinecone : prismrag_patch.adapters.pinecone.PineconeAdapter
|
|
12
|
+
- weaviate : prismrag_patch.adapters.weaviate.WeaviateAdapter
|
|
13
|
+
"""
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ChromaAdapter — prismrag-patch adapter for ChromaDB.
|
|
3
|
+
|
|
4
|
+
Requirements: pip install "prismrag-patch[chroma]"
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from prismrag_patch.core import PrismRAGPatch
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChromaAdapter:
|
|
18
|
+
"""
|
|
19
|
+
Wraps a ChromaDB collection with PrismRAG Tier-1 re-mapping.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
patch : PrismRAGPatch
|
|
24
|
+
Initialized PrismRAGPatch instance.
|
|
25
|
+
collection :
|
|
26
|
+
A ``chromadb.Collection`` (or compatible) object.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, patch: PrismRAGPatch, collection: Any) -> None:
|
|
30
|
+
self.patch = patch
|
|
31
|
+
self.collection = collection
|
|
32
|
+
|
|
33
|
+
def insert(
|
|
34
|
+
self,
|
|
35
|
+
text: str,
|
|
36
|
+
vector: List[float],
|
|
37
|
+
doc_id: Optional[str] = None,
|
|
38
|
+
metadata: Optional[Dict] = None,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Re-map *vector* then add to the collection. Returns the document id."""
|
|
41
|
+
result = self.patch.project(text, vector)
|
|
42
|
+
remapped = result["vector"]
|
|
43
|
+
doc_id = doc_id or str(uuid.uuid4())
|
|
44
|
+
meta = {**(metadata or {})}
|
|
45
|
+
if result["category"]:
|
|
46
|
+
meta["prismrag_category"] = result["category"].get("slug", "")
|
|
47
|
+
meta["prismrag_label"] = result["category"].get("label", "")
|
|
48
|
+
|
|
49
|
+
self.collection.add(
|
|
50
|
+
ids=[doc_id],
|
|
51
|
+
embeddings=[remapped],
|
|
52
|
+
documents=[text],
|
|
53
|
+
metadatas=[meta],
|
|
54
|
+
)
|
|
55
|
+
log.debug("chroma: inserted id=%s category=%s", doc_id, result["category"])
|
|
56
|
+
return doc_id
|
|
57
|
+
|
|
58
|
+
def batch_insert(
|
|
59
|
+
self,
|
|
60
|
+
records: List[Dict],
|
|
61
|
+
) -> List[str]:
|
|
62
|
+
"""
|
|
63
|
+
Insert multiple records in one ChromaDB call.
|
|
64
|
+
|
|
65
|
+
Each record must have ``text`` and ``vector``; ``doc_id`` and ``metadata`` are optional.
|
|
66
|
+
Returns a list of document IDs.
|
|
67
|
+
"""
|
|
68
|
+
ids_out, embeddings, documents, metadatas = [], [], [], []
|
|
69
|
+
for rec in records:
|
|
70
|
+
result = self.patch.project(rec["text"], rec["vector"])
|
|
71
|
+
doc_id = rec.get("doc_id") or str(uuid.uuid4())
|
|
72
|
+
meta = {**(rec.get("metadata") or {})}
|
|
73
|
+
if result["category"]:
|
|
74
|
+
meta["prismrag_category"] = result["category"].get("slug", "")
|
|
75
|
+
meta["prismrag_label"] = result["category"].get("label", "")
|
|
76
|
+
ids_out.append(doc_id)
|
|
77
|
+
embeddings.append(result["vector"])
|
|
78
|
+
documents.append(rec["text"])
|
|
79
|
+
metadatas.append(meta)
|
|
80
|
+
|
|
81
|
+
self.collection.add(ids=ids_out, embeddings=embeddings, documents=documents, metadatas=metadatas)
|
|
82
|
+
log.debug("chroma: batch inserted %d docs", len(ids_out))
|
|
83
|
+
return ids_out
|
|
84
|
+
|
|
85
|
+
def search(
|
|
86
|
+
self,
|
|
87
|
+
query_text: str,
|
|
88
|
+
query_vector: List[float],
|
|
89
|
+
top_k: int = 5,
|
|
90
|
+
where: Optional[Dict] = None,
|
|
91
|
+
) -> List[Dict]:
|
|
92
|
+
"""Re-map *query_vector* then query the collection."""
|
|
93
|
+
remapped = self.patch.remap_vector(query_vector, query_text)
|
|
94
|
+
kwargs: Dict[str, Any] = {
|
|
95
|
+
"query_embeddings": [remapped],
|
|
96
|
+
"n_results": top_k,
|
|
97
|
+
"include": ["documents", "metadatas", "distances"],
|
|
98
|
+
}
|
|
99
|
+
if where:
|
|
100
|
+
kwargs["where"] = where
|
|
101
|
+
|
|
102
|
+
res = self.collection.query(**kwargs)
|
|
103
|
+
results = []
|
|
104
|
+
for i, doc_id in enumerate(res["ids"][0]):
|
|
105
|
+
results.append({
|
|
106
|
+
"id": doc_id,
|
|
107
|
+
"text": res["documents"][0][i],
|
|
108
|
+
"metadata": res["metadatas"][0][i],
|
|
109
|
+
"score": 1.0 - float(res["distances"][0][i]),
|
|
110
|
+
})
|
|
111
|
+
return results
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PgvectorAdapter — prismrag-patch adapter for PostgreSQL + pgvector.
|
|
3
|
+
|
|
4
|
+
Requirements: pip install "prismrag-patch[pgvector]"
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from prismrag_patch.core import PrismRAGPatch
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PgvectorAdapter:
|
|
18
|
+
"""
|
|
19
|
+
Wraps a psycopg2 connection and a pgvector table with PrismRAG re-mapping.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
patch : PrismRAGPatch
|
|
24
|
+
Initialized PrismRAGPatch instance (validates license on init).
|
|
25
|
+
connection :
|
|
26
|
+
A psycopg2 connection (or compatible) object.
|
|
27
|
+
table : str
|
|
28
|
+
Table name. Expected columns: id SERIAL, text TEXT, vector vector(N), metadata JSONB.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
patch: PrismRAGPatch,
|
|
34
|
+
connection: Any,
|
|
35
|
+
table: str = "prismrag_chunks",
|
|
36
|
+
) -> None:
|
|
37
|
+
self.patch = patch
|
|
38
|
+
self.conn = connection
|
|
39
|
+
self.table = table
|
|
40
|
+
|
|
41
|
+
def ensure_table(self, dim: int = 1536) -> None:
|
|
42
|
+
"""Create the table and HNSW index if they do not exist."""
|
|
43
|
+
with self.conn.cursor() as cur:
|
|
44
|
+
cur.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
|
45
|
+
cur.execute(f"""
|
|
46
|
+
CREATE TABLE IF NOT EXISTS {self.table} (
|
|
47
|
+
id SERIAL PRIMARY KEY,
|
|
48
|
+
text TEXT NOT NULL,
|
|
49
|
+
vector vector({dim}) NOT NULL,
|
|
50
|
+
metadata JSONB DEFAULT '{{}}'::jsonb
|
|
51
|
+
)
|
|
52
|
+
""")
|
|
53
|
+
cur.execute(f"""
|
|
54
|
+
CREATE INDEX IF NOT EXISTS {self.table}_vector_idx
|
|
55
|
+
ON {self.table} USING hnsw (vector vector_cosine_ops)
|
|
56
|
+
WITH (m = 16, ef_construction = 64)
|
|
57
|
+
""")
|
|
58
|
+
self.conn.commit()
|
|
59
|
+
|
|
60
|
+
def insert(
|
|
61
|
+
self,
|
|
62
|
+
text: str,
|
|
63
|
+
vector: List[float],
|
|
64
|
+
metadata: Optional[Dict] = None,
|
|
65
|
+
) -> int:
|
|
66
|
+
"""Re-map *vector*, then insert into the table. Returns new row id."""
|
|
67
|
+
result = self.patch.project(text, vector)
|
|
68
|
+
remapped = result["vector"]
|
|
69
|
+
meta = {**(metadata or {})}
|
|
70
|
+
if result["category"]:
|
|
71
|
+
meta["prismrag_category"] = result["category"]
|
|
72
|
+
|
|
73
|
+
with self.conn.cursor() as cur:
|
|
74
|
+
cur.execute(
|
|
75
|
+
f"INSERT INTO {self.table} (text, vector, metadata) VALUES (%s, %s, %s) RETURNING id",
|
|
76
|
+
(text, remapped, json.dumps(meta)),
|
|
77
|
+
)
|
|
78
|
+
row_id = cur.fetchone()[0]
|
|
79
|
+
self.conn.commit()
|
|
80
|
+
log.debug("pgvector: inserted id=%s category=%s", row_id, result["category"])
|
|
81
|
+
return row_id
|
|
82
|
+
|
|
83
|
+
def batch_insert(
|
|
84
|
+
self,
|
|
85
|
+
records: List[Dict],
|
|
86
|
+
) -> List[int]:
|
|
87
|
+
"""
|
|
88
|
+
Insert multiple records in a single transaction.
|
|
89
|
+
|
|
90
|
+
Each record must have ``text`` and ``vector`` keys; ``metadata`` is optional.
|
|
91
|
+
Returns a list of inserted row IDs in the same order as *records*.
|
|
92
|
+
"""
|
|
93
|
+
ids = []
|
|
94
|
+
with self.conn.cursor() as cur:
|
|
95
|
+
for rec in records:
|
|
96
|
+
result = self.patch.project(rec["text"], rec["vector"])
|
|
97
|
+
remapped = result["vector"]
|
|
98
|
+
meta = {**(rec.get("metadata") or {})}
|
|
99
|
+
if result["category"]:
|
|
100
|
+
meta["prismrag_category"] = result["category"]
|
|
101
|
+
cur.execute(
|
|
102
|
+
f"INSERT INTO {self.table} (text, vector, metadata) VALUES (%s, %s, %s) RETURNING id",
|
|
103
|
+
(rec["text"], remapped, json.dumps(meta)),
|
|
104
|
+
)
|
|
105
|
+
ids.append(cur.fetchone()[0])
|
|
106
|
+
self.conn.commit()
|
|
107
|
+
log.debug("pgvector: batch inserted %d rows", len(ids))
|
|
108
|
+
return ids
|
|
109
|
+
|
|
110
|
+
def search(
|
|
111
|
+
self,
|
|
112
|
+
query_text: str,
|
|
113
|
+
query_vector: List[float],
|
|
114
|
+
top_k: int = 5,
|
|
115
|
+
) -> List[Dict]:
|
|
116
|
+
"""Re-map *query_vector* then do cosine similarity search."""
|
|
117
|
+
remapped = self.patch.remap_vector(query_vector, query_text)
|
|
118
|
+
with self.conn.cursor() as cur:
|
|
119
|
+
cur.execute(
|
|
120
|
+
f"""
|
|
121
|
+
SELECT id, text, metadata,
|
|
122
|
+
1 - (vector <=> %s::vector) AS score
|
|
123
|
+
FROM {self.table}
|
|
124
|
+
ORDER BY vector <=> %s::vector
|
|
125
|
+
LIMIT %s
|
|
126
|
+
""",
|
|
127
|
+
(remapped, remapped, top_k),
|
|
128
|
+
)
|
|
129
|
+
rows = cur.fetchall()
|
|
130
|
+
return [
|
|
131
|
+
{"id": r[0], "text": r[1], "metadata": r[2], "score": float(r[3])}
|
|
132
|
+
for r in rows
|
|
133
|
+
]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PineconeAdapter — prismrag-patch adapter for Pinecone.
|
|
3
|
+
|
|
4
|
+
Requirements: pip install "prismrag-patch[pinecone]"
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from prismrag_patch.core import PrismRAGPatch
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PineconeAdapter:
|
|
18
|
+
"""
|
|
19
|
+
Wraps a Pinecone Index with PrismRAG Tier-1 re-mapping.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
patch : PrismRAGPatch
|
|
24
|
+
Initialized PrismRAGPatch instance.
|
|
25
|
+
index :
|
|
26
|
+
A ``pinecone.Index`` (v3 client) object.
|
|
27
|
+
namespace : str
|
|
28
|
+
Pinecone namespace (default ``""``).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
patch: PrismRAGPatch,
|
|
34
|
+
index: Any,
|
|
35
|
+
namespace: str = "",
|
|
36
|
+
) -> None:
|
|
37
|
+
self.patch = patch
|
|
38
|
+
self.index = index
|
|
39
|
+
self.namespace = namespace
|
|
40
|
+
|
|
41
|
+
def upsert(
|
|
42
|
+
self,
|
|
43
|
+
text: str,
|
|
44
|
+
vector: List[float],
|
|
45
|
+
doc_id: Optional[str] = None,
|
|
46
|
+
metadata: Optional[Dict] = None,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Re-map *vector* then upsert into the index. Returns the vector id."""
|
|
49
|
+
result = self.patch.project(text, vector)
|
|
50
|
+
remapped = result["vector"]
|
|
51
|
+
doc_id = doc_id or str(uuid.uuid4())
|
|
52
|
+
meta = {"text": text, **(metadata or {})}
|
|
53
|
+
if result["category"]:
|
|
54
|
+
meta["prismrag_category"] = result["category"].get("slug", "")
|
|
55
|
+
meta["prismrag_label"] = result["category"].get("label", "")
|
|
56
|
+
|
|
57
|
+
self.index.upsert(
|
|
58
|
+
vectors=[{"id": doc_id, "values": remapped, "metadata": meta}],
|
|
59
|
+
namespace=self.namespace,
|
|
60
|
+
)
|
|
61
|
+
log.debug("pinecone: upserted id=%s category=%s", doc_id, result["category"])
|
|
62
|
+
return doc_id
|
|
63
|
+
|
|
64
|
+
# Alias so the API matches the other adapters
|
|
65
|
+
insert = upsert
|
|
66
|
+
|
|
67
|
+
def batch_insert(
|
|
68
|
+
self,
|
|
69
|
+
records: List[Dict],
|
|
70
|
+
) -> List[str]:
|
|
71
|
+
"""
|
|
72
|
+
Upsert multiple records in one Pinecone call.
|
|
73
|
+
|
|
74
|
+
Each record must have ``text`` and ``vector``; ``doc_id``, ``metadata``, and ``namespace`` are optional.
|
|
75
|
+
Returns a list of vector IDs.
|
|
76
|
+
"""
|
|
77
|
+
vectors = []
|
|
78
|
+
ids_out = []
|
|
79
|
+
for rec in records:
|
|
80
|
+
result = self.patch.project(rec["text"], rec["vector"])
|
|
81
|
+
doc_id = rec.get("doc_id") or str(uuid.uuid4())
|
|
82
|
+
meta = {"text": rec["text"], **(rec.get("metadata") or {})}
|
|
83
|
+
if result["category"]:
|
|
84
|
+
meta["prismrag_category"] = result["category"].get("slug", "")
|
|
85
|
+
meta["prismrag_label"] = result["category"].get("label", "")
|
|
86
|
+
vectors.append({"id": doc_id, "values": result["vector"], "metadata": meta})
|
|
87
|
+
ids_out.append(doc_id)
|
|
88
|
+
|
|
89
|
+
self.index.upsert(vectors=vectors, namespace=self.namespace)
|
|
90
|
+
log.debug("pinecone: batch upserted %d vectors", len(ids_out))
|
|
91
|
+
return ids_out
|
|
92
|
+
|
|
93
|
+
def search(
|
|
94
|
+
self,
|
|
95
|
+
query_text: str,
|
|
96
|
+
query_vector: List[float],
|
|
97
|
+
top_k: int = 5,
|
|
98
|
+
filter: Optional[Dict] = None,
|
|
99
|
+
) -> List[Dict]:
|
|
100
|
+
"""Re-map *query_vector* then query the index."""
|
|
101
|
+
remapped = self.patch.remap_vector(query_vector, query_text)
|
|
102
|
+
kwargs: Dict[str, Any] = {
|
|
103
|
+
"vector": remapped,
|
|
104
|
+
"top_k": top_k,
|
|
105
|
+
"include_metadata": True,
|
|
106
|
+
"namespace": self.namespace,
|
|
107
|
+
}
|
|
108
|
+
if filter:
|
|
109
|
+
kwargs["filter"] = filter
|
|
110
|
+
|
|
111
|
+
res = self.index.query(**kwargs)
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
"id": m.id,
|
|
115
|
+
"text": m.metadata.get("text", ""),
|
|
116
|
+
"metadata": m.metadata,
|
|
117
|
+
"score": float(m.score),
|
|
118
|
+
}
|
|
119
|
+
for m in res.matches
|
|
120
|
+
]
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WeaviateAdapter — prismrag-patch adapter for Weaviate.
|
|
3
|
+
|
|
4
|
+
Requirements: pip install "prismrag-patch[weaviate]"
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Any, Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from prismrag_patch.core import PrismRAGPatch
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WeaviateAdapter:
|
|
18
|
+
"""
|
|
19
|
+
Wraps a Weaviate v4 client collection with PrismRAG Tier-1 re-mapping.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
patch : PrismRAGPatch
|
|
24
|
+
Initialized PrismRAGPatch instance.
|
|
25
|
+
collection :
|
|
26
|
+
A Weaviate v4 ``Collection`` object (``client.collections.get("MyClass")``).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, patch: PrismRAGPatch, collection: Any) -> None:
|
|
30
|
+
self.patch = patch
|
|
31
|
+
self.collection = collection
|
|
32
|
+
|
|
33
|
+
def insert(
|
|
34
|
+
self,
|
|
35
|
+
text: str,
|
|
36
|
+
vector: List[float],
|
|
37
|
+
doc_id: Optional[str] = None,
|
|
38
|
+
properties: Optional[Dict] = None,
|
|
39
|
+
) -> str:
|
|
40
|
+
"""Re-map *vector* then insert into the collection. Returns the UUID."""
|
|
41
|
+
result = self.patch.project(text, vector)
|
|
42
|
+
remapped = result["vector"]
|
|
43
|
+
doc_uuid = doc_id or str(uuid.uuid4())
|
|
44
|
+
props = {"text": text, **(properties or {})}
|
|
45
|
+
if result["category"]:
|
|
46
|
+
props["prismrag_category"] = result["category"].get("slug", "")
|
|
47
|
+
props["prismrag_label"] = result["category"].get("label", "")
|
|
48
|
+
|
|
49
|
+
self.collection.data.insert(
|
|
50
|
+
properties=props,
|
|
51
|
+
vector=remapped,
|
|
52
|
+
uuid=doc_uuid,
|
|
53
|
+
)
|
|
54
|
+
log.debug("weaviate: inserted uuid=%s category=%s", doc_uuid, result["category"])
|
|
55
|
+
return doc_uuid
|
|
56
|
+
|
|
57
|
+
def batch_insert(
|
|
58
|
+
self,
|
|
59
|
+
records: List[Dict],
|
|
60
|
+
) -> List[str]:
|
|
61
|
+
"""
|
|
62
|
+
Insert multiple objects using Weaviate v4 batch context manager.
|
|
63
|
+
|
|
64
|
+
Each record must have ``text`` and ``vector``; ``doc_id`` and ``properties`` are optional.
|
|
65
|
+
Returns a list of UUID strings.
|
|
66
|
+
"""
|
|
67
|
+
ids_out = []
|
|
68
|
+
with self.collection.batch.dynamic() as batch:
|
|
69
|
+
for rec in records:
|
|
70
|
+
result = self.patch.project(rec["text"], rec["vector"])
|
|
71
|
+
doc_uuid = rec.get("doc_id") or str(uuid.uuid4())
|
|
72
|
+
props = {"text": rec["text"], **(rec.get("properties") or {})}
|
|
73
|
+
if result["category"]:
|
|
74
|
+
props["prismrag_category"] = result["category"].get("slug", "")
|
|
75
|
+
props["prismrag_label"] = result["category"].get("label", "")
|
|
76
|
+
batch.add_object(properties=props, vector=result["vector"], uuid=doc_uuid)
|
|
77
|
+
ids_out.append(doc_uuid)
|
|
78
|
+
log.debug("weaviate: batch inserted %d objects", len(ids_out))
|
|
79
|
+
return ids_out
|
|
80
|
+
|
|
81
|
+
def search(
|
|
82
|
+
self,
|
|
83
|
+
query_text: str,
|
|
84
|
+
query_vector: List[float],
|
|
85
|
+
top_k: int = 5,
|
|
86
|
+
filters: Optional[Any] = None,
|
|
87
|
+
) -> List[Dict]:
|
|
88
|
+
"""Re-map *query_vector* then perform a near-vector search."""
|
|
89
|
+
remapped = self.patch.remap_vector(query_vector, query_text)
|
|
90
|
+
query = self.collection.query.near_vector(
|
|
91
|
+
near_vector=remapped,
|
|
92
|
+
limit=top_k,
|
|
93
|
+
return_metadata=["score", "distance"],
|
|
94
|
+
)
|
|
95
|
+
if filters:
|
|
96
|
+
query = self.collection.query.near_vector(
|
|
97
|
+
near_vector=remapped,
|
|
98
|
+
limit=top_k,
|
|
99
|
+
filters=filters,
|
|
100
|
+
return_metadata=["score", "distance"],
|
|
101
|
+
)
|
|
102
|
+
response = query # weaviate v4: near_vector returns the response directly
|
|
103
|
+
results = []
|
|
104
|
+
for obj in response.objects:
|
|
105
|
+
results.append({
|
|
106
|
+
"id": str(obj.uuid),
|
|
107
|
+
"text": obj.properties.get("text", ""),
|
|
108
|
+
"properties": obj.properties,
|
|
109
|
+
"score": obj.metadata.score if obj.metadata else None,
|
|
110
|
+
})
|
|
111
|
+
return results
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrismRAGPatch — Tier-1 deterministic category projection.
|
|
3
|
+
|
|
4
|
+
The re-mapping algorithm projects a raw embedding vector onto the nearest
|
|
5
|
+
category centroid before storage/retrieval, grounding every chunk in your
|
|
6
|
+
verified taxonomy and eliminating the main hallucination path.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from prismrag_patch.license import LicenseError, validate_license
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PrismRAGPatch:
|
|
21
|
+
"""
|
|
22
|
+
Core PrismRAG re-mapping engine.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
license_key : str
|
|
27
|
+
Your ``prlib_`` license key.
|
|
28
|
+
mapping : dict
|
|
29
|
+
Mapping definition with ``categories`` and ``rules`` lists.
|
|
30
|
+
Example::
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
"categories": [
|
|
34
|
+
{"slug": "risk", "label": "Risk & Compliance"},
|
|
35
|
+
{"slug": "growth", "label": "Growth"},
|
|
36
|
+
],
|
|
37
|
+
"rules": [
|
|
38
|
+
{"word": "volatility", "category_slug": "risk", "weight": 1.0},
|
|
39
|
+
{"word": "revenue", "category_slug": "growth", "weight": 1.0},
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
blend_alpha : float
|
|
43
|
+
Blend factor [0, 1]. 0 = pure original vector, 1 = full projection.
|
|
44
|
+
Default 0.35 gives strong grounding without losing semantic richness.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
license_key: str,
|
|
50
|
+
mapping: Dict[str, Any],
|
|
51
|
+
blend_alpha: float = 0.35,
|
|
52
|
+
) -> None:
|
|
53
|
+
self._license_info = validate_license(license_key)
|
|
54
|
+
self.mapping = mapping
|
|
55
|
+
self.blend_alpha = float(blend_alpha)
|
|
56
|
+
|
|
57
|
+
# Build category index {slug -> index}
|
|
58
|
+
self._categories: List[Dict] = mapping.get("categories", [])
|
|
59
|
+
self._cat_index: Dict[str, int] = {
|
|
60
|
+
c["slug"]: i for i, c in enumerate(self._categories)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Compile rule lookup {word -> (category_index, weight)}
|
|
64
|
+
self._rules: Dict[str, tuple] = {}
|
|
65
|
+
for rule in mapping.get("rules", []):
|
|
66
|
+
slug = rule.get("category_slug", "")
|
|
67
|
+
idx = self._cat_index.get(slug)
|
|
68
|
+
if idx is not None:
|
|
69
|
+
self._rules[rule["word"].lower()] = (idx, float(rule.get("weight", 1.0)))
|
|
70
|
+
|
|
71
|
+
log.info(
|
|
72
|
+
"prismrag-patch: initialized — %d categories, %d rules, alpha=%.2f",
|
|
73
|
+
len(self._categories), len(self._rules), self.blend_alpha,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# Public API
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
def remap_vector(self, vector: List[float], text: str = "") -> List[float]:
|
|
81
|
+
"""
|
|
82
|
+
Apply Tier-1 projection to *vector*.
|
|
83
|
+
|
|
84
|
+
If *text* is supplied the category is inferred from rule matches.
|
|
85
|
+
If no rules match the vector is returned unchanged (safe fallback).
|
|
86
|
+
"""
|
|
87
|
+
v = np.array(vector, dtype=np.float32)
|
|
88
|
+
cat_idx = self._infer_category(text) if text else None
|
|
89
|
+
if cat_idx is None:
|
|
90
|
+
return vector # no re-mapping signal
|
|
91
|
+
|
|
92
|
+
# Build a one-hot direction vector in embedding space:
|
|
93
|
+
# The projection direction is a unit vector in the dimension whose
|
|
94
|
+
# index corresponds to the winning category, broadcast to match v.
|
|
95
|
+
dim = len(v)
|
|
96
|
+
direction = np.zeros(dim, dtype=np.float32)
|
|
97
|
+
# Distribute category signal evenly across dimensions assigned to the
|
|
98
|
+
# category cluster (simple but effective without a learned centroid).
|
|
99
|
+
cluster_size = max(1, dim // max(1, len(self._categories)))
|
|
100
|
+
start = (cat_idx * cluster_size) % dim
|
|
101
|
+
end = min(start + cluster_size, dim)
|
|
102
|
+
direction[start:end] = 1.0
|
|
103
|
+
norm = np.linalg.norm(direction)
|
|
104
|
+
if norm > 0:
|
|
105
|
+
direction /= norm
|
|
106
|
+
|
|
107
|
+
# Blend: v' = (1 - alpha) * v + alpha * ||v|| * direction
|
|
108
|
+
v_norm = np.linalg.norm(v)
|
|
109
|
+
remapped = (1.0 - self.blend_alpha) * v + self.blend_alpha * v_norm * direction
|
|
110
|
+
|
|
111
|
+
# Re-normalize to unit sphere (matches most embedding conventions)
|
|
112
|
+
r_norm = np.linalg.norm(remapped)
|
|
113
|
+
if r_norm > 0:
|
|
114
|
+
remapped /= r_norm
|
|
115
|
+
return remapped.tolist()
|
|
116
|
+
|
|
117
|
+
def project(self, text: str, vector: List[float]) -> Dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Full projection: infer category, remap vector, return enriched record.
|
|
120
|
+
"""
|
|
121
|
+
cat_idx = self._infer_category(text)
|
|
122
|
+
cat = self._categories[cat_idx] if cat_idx is not None else None
|
|
123
|
+
remapped = self.remap_vector(vector, text)
|
|
124
|
+
return {
|
|
125
|
+
"vector": remapped,
|
|
126
|
+
"category": cat,
|
|
127
|
+
"original_vector": vector,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def category_for(self, text: str) -> Optional[Dict]:
|
|
131
|
+
"""Return the matched category dict, or None."""
|
|
132
|
+
idx = self._infer_category(text)
|
|
133
|
+
return self._categories[idx] if idx is not None else None
|
|
134
|
+
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
# Internal helpers
|
|
137
|
+
# ------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def _infer_category(self, text: str) -> Optional[int]:
|
|
140
|
+
"""Score rules against *text*, return category index of winner."""
|
|
141
|
+
tokens = text.lower().split()
|
|
142
|
+
scores: Dict[int, float] = {}
|
|
143
|
+
for token in tokens:
|
|
144
|
+
match = self._rules.get(token)
|
|
145
|
+
if match:
|
|
146
|
+
cat_idx, weight = match
|
|
147
|
+
scores[cat_idx] = scores.get(cat_idx, 0.0) + weight
|
|
148
|
+
if not scores:
|
|
149
|
+
return None
|
|
150
|
+
return max(scores, key=lambda k: scores[k])
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
License validation for prismrag-patch.
|
|
3
|
+
|
|
4
|
+
Validates against https://prismrag.insightits.com/api/v1/lib/validate.
|
|
5
|
+
- First call: hits the API, caches result locally for 23 hours.
|
|
6
|
+
- Offline: 7-day grace period before raising LicenseError.
|
|
7
|
+
- Grace period exceeded: raises LicenseError with a clear message.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
import requests
|
|
20
|
+
|
|
21
|
+
log = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
VALIDATE_URL = os.getenv(
|
|
24
|
+
"PRISMRAG_LICENSE_URL",
|
|
25
|
+
"https://prismrag.insightits.com/api/v1/lib/validate",
|
|
26
|
+
)
|
|
27
|
+
CACHE_TTL_SECONDS = 23 * 3600 # re-validate every 23 hours
|
|
28
|
+
GRACE_PERIOD_DAYS = 7 # offline grace period
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LicenseError(RuntimeError):
|
|
32
|
+
"""Raised when the license key is invalid, expired, or revoked."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cache_path(key: str) -> Path:
|
|
36
|
+
slug = hashlib.sha256(key.encode()).hexdigest()[:16]
|
|
37
|
+
base = Path(os.getenv("PRISMRAG_CACHE_DIR", Path.home() / ".cache" / "prismrag_patch"))
|
|
38
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
return base / f"lic_{slug}.json"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _read_cache(key: str) -> Optional[dict]:
|
|
43
|
+
try:
|
|
44
|
+
data = json.loads(_cache_path(key).read_text())
|
|
45
|
+
if time.time() - data.get("cached_at", 0) < CACHE_TTL_SECONDS:
|
|
46
|
+
return data
|
|
47
|
+
return None # stale
|
|
48
|
+
except Exception:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _write_cache(key: str, payload: dict) -> None:
|
|
53
|
+
try:
|
|
54
|
+
payload["cached_at"] = time.time()
|
|
55
|
+
_cache_path(key).write_text(json.dumps(payload))
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _last_valid_at(key: str) -> Optional[float]:
|
|
61
|
+
"""Return timestamp of last successful validation, or None."""
|
|
62
|
+
try:
|
|
63
|
+
data = json.loads(_cache_path(key).read_text())
|
|
64
|
+
return data.get("validated_at")
|
|
65
|
+
except Exception:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def validate_license(key: str, product: str = "prismrag-patch") -> dict:
|
|
70
|
+
"""
|
|
71
|
+
Validate *key* against the PrismRAG license server.
|
|
72
|
+
|
|
73
|
+
Returns the license metadata dict on success.
|
|
74
|
+
Raises LicenseError on failure.
|
|
75
|
+
"""
|
|
76
|
+
if not key or not key.startswith("prlib_"):
|
|
77
|
+
raise LicenseError(
|
|
78
|
+
"Invalid license key format. Keys start with 'prlib_'. "
|
|
79
|
+
"Get yours at https://prismrag.insightits.com/prismrag-lib.html"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# 1. Check cache
|
|
83
|
+
cached = _read_cache(key)
|
|
84
|
+
if cached:
|
|
85
|
+
if cached.get("valid"):
|
|
86
|
+
log.debug("prismrag-patch: license valid (cached)")
|
|
87
|
+
return cached
|
|
88
|
+
raise LicenseError(cached.get("message", "License invalid (cached)"))
|
|
89
|
+
|
|
90
|
+
# 2. Call API
|
|
91
|
+
try:
|
|
92
|
+
resp = requests.post(
|
|
93
|
+
VALIDATE_URL,
|
|
94
|
+
json={"license_key": key, "product": product},
|
|
95
|
+
timeout=8,
|
|
96
|
+
)
|
|
97
|
+
data = resp.json()
|
|
98
|
+
except requests.RequestException as exc:
|
|
99
|
+
# Offline — check grace period
|
|
100
|
+
last = _last_valid_at(key)
|
|
101
|
+
if last and (time.time() - last) < GRACE_PERIOD_DAYS * 86400:
|
|
102
|
+
log.warning("prismrag-patch: offline, using grace period (%d days left)",
|
|
103
|
+
GRACE_PERIOD_DAYS - int((time.time() - last) / 86400))
|
|
104
|
+
return {"valid": True, "offline_grace": True}
|
|
105
|
+
raise LicenseError(
|
|
106
|
+
f"Cannot reach license server and grace period exceeded. "
|
|
107
|
+
f"Check your internet connection or contact support@prismrag.insightits.com. ({exc})"
|
|
108
|
+
) from exc
|
|
109
|
+
|
|
110
|
+
if not data.get("valid"):
|
|
111
|
+
_write_cache(key, {**data, "validated_at": time.time()})
|
|
112
|
+
raise LicenseError(data.get("message", "License key rejected by server."))
|
|
113
|
+
|
|
114
|
+
data["validated_at"] = time.time()
|
|
115
|
+
_write_cache(key, data)
|
|
116
|
+
log.debug("prismrag-patch: license valid (server confirmed)")
|
|
117
|
+
return data
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prismrag-patch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drop-in hallucination-resistant retrieval for pgvector, ChromaDB, Pinecone, and Weaviate
|
|
5
|
+
Author-email: Insight IT Solutions <sales@prismrag.insightits.com>
|
|
6
|
+
License: Commercial
|
|
7
|
+
Project-URL: Homepage, https://prismrag.insightits.com/prismrag-lib.html
|
|
8
|
+
Project-URL: Repository, https://github.com/aminparva84/InsightPrismRAG
|
|
9
|
+
Project-URL: Bug Tracker, https://prismrag.insightits.com/support
|
|
10
|
+
Keywords: rag,vector-database,hallucination,pgvector,chromadb,pinecone,weaviate,llm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: requests>=2.28
|
|
23
|
+
Requires-Dist: numpy>=1.24
|
|
24
|
+
Provides-Extra: pgvector
|
|
25
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "pgvector"
|
|
26
|
+
Requires-Dist: pgvector>=0.2; extra == "pgvector"
|
|
27
|
+
Provides-Extra: chroma
|
|
28
|
+
Requires-Dist: chromadb>=0.4; extra == "chroma"
|
|
29
|
+
Provides-Extra: pinecone
|
|
30
|
+
Requires-Dist: pinecone-client>=3.0; extra == "pinecone"
|
|
31
|
+
Provides-Extra: weaviate
|
|
32
|
+
Requires-Dist: weaviate-client>=4.0; extra == "weaviate"
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "all"
|
|
35
|
+
Requires-Dist: pgvector>=0.2; extra == "all"
|
|
36
|
+
Requires-Dist: chromadb>=0.4; extra == "all"
|
|
37
|
+
Requires-Dist: pinecone-client>=3.0; extra == "all"
|
|
38
|
+
Requires-Dist: weaviate-client>=4.0; extra == "all"
|
|
39
|
+
|
|
40
|
+
# prismrag-patch
|
|
41
|
+
|
|
42
|
+
**Drop-in hallucination-resistant retrieval for your own vector database.**
|
|
43
|
+
|
|
44
|
+
PrismRAG Patch wraps pgvector, ChromaDB, Pinecone, or Weaviate with PrismRAG's
|
|
45
|
+
Tier-1 re-mapping technique — deterministic category projection that grounds every
|
|
46
|
+
chunk in your verified rules before it ever reaches the LLM.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from prismrag_patch import PrismRAGPatch
|
|
50
|
+
from prismrag_patch.adapters.pgvector import PgvectorAdapter
|
|
51
|
+
|
|
52
|
+
mapping = {
|
|
53
|
+
"categories": [
|
|
54
|
+
{"slug": "risk", "label": "Risk & Compliance"},
|
|
55
|
+
{"slug": "growth", "label": "Growth"},
|
|
56
|
+
],
|
|
57
|
+
"rules": [
|
|
58
|
+
{"word": "volatility", "category_slug": "risk", "weight": 1.0},
|
|
59
|
+
{"word": "revenue", "category_slug": "growth", "weight": 1.0},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
patch = PrismRAGPatch(license_key="prlib_…", mapping=mapping)
|
|
64
|
+
adapter = PgvectorAdapter(patch, connection=your_psycopg2_conn)
|
|
65
|
+
|
|
66
|
+
# Insert — re-mapped before storage
|
|
67
|
+
adapter.insert(text=doc, vector=embed(doc))
|
|
68
|
+
|
|
69
|
+
# Search — query re-mapped identically, no hallucination path
|
|
70
|
+
results = adapter.search("what is our risk exposure?", query_vector=q_vec)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install prismrag-patch # core only
|
|
77
|
+
pip install "prismrag-patch[pgvector]" # + pgvector support
|
|
78
|
+
pip install "prismrag-patch[chroma]" # + ChromaDB support
|
|
79
|
+
pip install "prismrag-patch[pinecone]" # + Pinecone support
|
|
80
|
+
pip install "prismrag-patch[weaviate]" # + Weaviate support
|
|
81
|
+
pip install "prismrag-patch[all]" # all adapters
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Commercial license required. Get yours at
|
|
87
|
+
[prismrag.insightits.com/prismrag-lib.html](https://prismrag.insightits.com/prismrag-lib.html).
|
|
88
|
+
|
|
89
|
+
© 2026 Insight IT Solutions
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
prismrag_patch/__init__.py
|
|
4
|
+
prismrag_patch/core.py
|
|
5
|
+
prismrag_patch/license.py
|
|
6
|
+
prismrag_patch.egg-info/PKG-INFO
|
|
7
|
+
prismrag_patch.egg-info/SOURCES.txt
|
|
8
|
+
prismrag_patch.egg-info/dependency_links.txt
|
|
9
|
+
prismrag_patch.egg-info/requires.txt
|
|
10
|
+
prismrag_patch.egg-info/top_level.txt
|
|
11
|
+
prismrag_patch/adapters/__init__.py
|
|
12
|
+
prismrag_patch/adapters/chroma.py
|
|
13
|
+
prismrag_patch/adapters/pgvector.py
|
|
14
|
+
prismrag_patch/adapters/pinecone.py
|
|
15
|
+
prismrag_patch/adapters/weaviate.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
requests>=2.28
|
|
2
|
+
numpy>=1.24
|
|
3
|
+
|
|
4
|
+
[all]
|
|
5
|
+
psycopg2-binary>=2.9
|
|
6
|
+
pgvector>=0.2
|
|
7
|
+
chromadb>=0.4
|
|
8
|
+
pinecone-client>=3.0
|
|
9
|
+
weaviate-client>=4.0
|
|
10
|
+
|
|
11
|
+
[chroma]
|
|
12
|
+
chromadb>=0.4
|
|
13
|
+
|
|
14
|
+
[pgvector]
|
|
15
|
+
psycopg2-binary>=2.9
|
|
16
|
+
pgvector>=0.2
|
|
17
|
+
|
|
18
|
+
[pinecone]
|
|
19
|
+
pinecone-client>=3.0
|
|
20
|
+
|
|
21
|
+
[weaviate]
|
|
22
|
+
weaviate-client>=4.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
prismrag_patch
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "prismrag-patch"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Drop-in hallucination-resistant retrieval for pgvector, ChromaDB, Pinecone, and Weaviate"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "Commercial" }
|
|
11
|
+
authors = [{ name = "Insight IT Solutions", email = "sales@prismrag.insightits.com" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
keywords = ["rag", "vector-database", "hallucination", "pgvector", "chromadb", "pinecone", "weaviate", "llm"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"requests>=2.28",
|
|
27
|
+
"numpy>=1.24",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
pgvector = ["psycopg2-binary>=2.9", "pgvector>=0.2"]
|
|
32
|
+
chroma = ["chromadb>=0.4"]
|
|
33
|
+
pinecone = ["pinecone-client>=3.0"]
|
|
34
|
+
weaviate = ["weaviate-client>=4.0"]
|
|
35
|
+
all = [
|
|
36
|
+
"psycopg2-binary>=2.9", "pgvector>=0.2",
|
|
37
|
+
"chromadb>=0.4",
|
|
38
|
+
"pinecone-client>=3.0",
|
|
39
|
+
"weaviate-client>=4.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://prismrag.insightits.com/prismrag-lib.html"
|
|
44
|
+
Repository = "https://github.com/aminparva84/InsightPrismRAG"
|
|
45
|
+
"Bug Tracker" = "https://prismrag.insightits.com/support"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["."]
|
|
49
|
+
include = ["prismrag_patch*"]
|