prismcortex 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prismcortex/__init__.py +40 -0
- prismcortex/adapters/__init__.py +20 -0
- prismcortex/adapters/ann.py +104 -0
- prismcortex/adapters/prism.py +174 -0
- prismcortex/adapters/reference.py +381 -0
- prismcortex/auth.py +81 -0
- prismcortex/determinism.py +75 -0
- prismcortex/engine.py +524 -0
- prismcortex/factory.py +48 -0
- prismcortex/labels.py +114 -0
- prismcortex/licensing.py +94 -0
- prismcortex/llm/__init__.py +1 -0
- prismcortex/llm/gemini.py +176 -0
- prismcortex/models.py +207 -0
- prismcortex/policy.py +64 -0
- prismcortex/ports.py +121 -0
- prismcortex/salience.py +44 -0
- prismcortex/server.py +520 -0
- prismcortex/server_helpers.py +74 -0
- prismcortex/static/index.html +94 -0
- prismcortex/tenant.py +103 -0
- prismcortex/tracing.py +85 -0
- prismcortex-0.2.1.dist-info/METADATA +175 -0
- prismcortex-0.2.1.dist-info/RECORD +27 -0
- prismcortex-0.2.1.dist-info/WHEEL +5 -0
- prismcortex-0.2.1.dist-info/licenses/LICENSE +21 -0
- prismcortex-0.2.1.dist-info/top_level.txt +1 -0
prismcortex/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""PrismCortex — deterministic, auditable, self-consolidating memory for AI agents.
|
|
2
|
+
|
|
3
|
+
from prismcortex import reference_memory
|
|
4
|
+
mem = reference_memory() # reference adapters + real Gemini
|
|
5
|
+
mem.digest("My deploy budget is $40k.")
|
|
6
|
+
mem.recall("What's my deploy budget?").answer
|
|
7
|
+
|
|
8
|
+
Swap the reference adapters for the real Insight ITS packages (prismlang, prismrag,
|
|
9
|
+
prismresonance, prismlib, Chorus) one port at a time — the engine never changes.
|
|
10
|
+
"""
|
|
11
|
+
from .engine import Memory
|
|
12
|
+
from .factory import reference_memory
|
|
13
|
+
from .models import (
|
|
14
|
+
Band,
|
|
15
|
+
DigestOutcome,
|
|
16
|
+
DigestResult,
|
|
17
|
+
Edge,
|
|
18
|
+
GraphVersion,
|
|
19
|
+
Node,
|
|
20
|
+
RecallResult,
|
|
21
|
+
StateDelta,
|
|
22
|
+
Subgraph,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__version__ = "0.2.1"
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Memory",
|
|
29
|
+
"reference_memory",
|
|
30
|
+
"Band",
|
|
31
|
+
"DigestOutcome",
|
|
32
|
+
"DigestResult",
|
|
33
|
+
"RecallResult",
|
|
34
|
+
"Node",
|
|
35
|
+
"Edge",
|
|
36
|
+
"Subgraph",
|
|
37
|
+
"StateDelta",
|
|
38
|
+
"GraphVersion",
|
|
39
|
+
"__version__",
|
|
40
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Adapters that satisfy the PrismCortex ports."""
|
|
2
|
+
from .reference import (
|
|
3
|
+
DurableCache,
|
|
4
|
+
HashingProjector,
|
|
5
|
+
InMemoryGraphStore,
|
|
6
|
+
InProcessMesh,
|
|
7
|
+
InProcessResonance,
|
|
8
|
+
ListStaging,
|
|
9
|
+
LocalBlobStore,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DurableCache",
|
|
14
|
+
"HashingProjector",
|
|
15
|
+
"InMemoryGraphStore",
|
|
16
|
+
"InProcessMesh",
|
|
17
|
+
"InProcessResonance",
|
|
18
|
+
"ListStaging",
|
|
19
|
+
"LocalBlobStore",
|
|
20
|
+
]
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""IVF-style ANN retrieval for graphs beyond ~10k nodes (numpy-only, no extra deps)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from .reference import InMemoryGraphStore
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AnnGraphStore(InMemoryGraphStore):
|
|
13
|
+
"""In-memory bitemporal store with inverted-file ANN when node count exceeds threshold."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, *, tenant_id: str = "default", ivf_threshold: Optional[int] = None, nlist: int = 256, nprobe: int = 16) -> None:
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.tenant_id = tenant_id
|
|
18
|
+
self._ivf_threshold = ivf_threshold or int(os.environ.get("PRISMCORTEX_ANN_THRESHOLD", "5000"))
|
|
19
|
+
self._nlist = nlist
|
|
20
|
+
self._nprobe = nprobe
|
|
21
|
+
self._centroids: Optional[np.ndarray] = None
|
|
22
|
+
self._inverted: list[list[str]] = []
|
|
23
|
+
self._ivf_dirty = True
|
|
24
|
+
|
|
25
|
+
def _rebuild_ivf(self) -> None:
|
|
26
|
+
self._ensure_matrix()
|
|
27
|
+
if self._emb_unit is None or len(self._emb_ids) < self._ivf_threshold:
|
|
28
|
+
self._centroids = None
|
|
29
|
+
self._inverted = []
|
|
30
|
+
self._ivf_dirty = False
|
|
31
|
+
return
|
|
32
|
+
n, d = self._emb_unit.shape
|
|
33
|
+
k = min(self._nlist, max(8, n // 40))
|
|
34
|
+
rng = np.random.default_rng(42)
|
|
35
|
+
idx = rng.choice(n, size=k, replace=False)
|
|
36
|
+
self._centroids = self._emb_unit[idx].copy()
|
|
37
|
+
# Lloyd-lite: 3 iterations
|
|
38
|
+
for _ in range(3):
|
|
39
|
+
sims = self._emb_unit @ self._centroids.T
|
|
40
|
+
assign = np.argmax(sims, axis=1)
|
|
41
|
+
for c in range(k):
|
|
42
|
+
mask = assign == c
|
|
43
|
+
if mask.any():
|
|
44
|
+
self._centroids[c] = self._emb_unit[mask].mean(axis=0)
|
|
45
|
+
cn = np.linalg.norm(self._centroids[c]) or 1.0
|
|
46
|
+
self._centroids[c] /= cn
|
|
47
|
+
self._inverted = [[] for _ in range(k)]
|
|
48
|
+
sims = self._emb_unit @ self._centroids.T
|
|
49
|
+
assign = np.argmax(sims, axis=1)
|
|
50
|
+
for i, c in enumerate(assign):
|
|
51
|
+
self._inverted[int(c)].append(self._emb_ids[i])
|
|
52
|
+
self._ivf_dirty = False
|
|
53
|
+
|
|
54
|
+
def _ensure_matrix(self) -> None:
|
|
55
|
+
super()._ensure_matrix()
|
|
56
|
+
if self._matrix_dirty:
|
|
57
|
+
self._ivf_dirty = True
|
|
58
|
+
|
|
59
|
+
def apply(self, delta):
|
|
60
|
+
v = super().apply(delta)
|
|
61
|
+
self._ivf_dirty = True
|
|
62
|
+
return v
|
|
63
|
+
|
|
64
|
+
def retrieve(self, embedding: list[float], k: int = 8):
|
|
65
|
+
if not self._nodes:
|
|
66
|
+
return super().retrieve(embedding, k)
|
|
67
|
+
self._ensure_matrix()
|
|
68
|
+
if self._emb_unit is None:
|
|
69
|
+
return super().retrieve(embedding, k)
|
|
70
|
+
if len(self._emb_ids) < self._ivf_threshold:
|
|
71
|
+
return super().retrieve(embedding, k)
|
|
72
|
+
if self._ivf_dirty:
|
|
73
|
+
self._rebuild_ivf()
|
|
74
|
+
if self._centroids is None:
|
|
75
|
+
return super().retrieve(embedding, k)
|
|
76
|
+
|
|
77
|
+
q = np.asarray(embedding, dtype=np.float32)
|
|
78
|
+
qn = float(np.linalg.norm(q)) or 1.0
|
|
79
|
+
q = q / qn
|
|
80
|
+
csim = self._centroids @ q
|
|
81
|
+
n = len(self._emb_ids)
|
|
82
|
+
# Scale probe depth with graph size (more clusters searched at 50k+ nodes).
|
|
83
|
+
nprobe = min(len(csim), max(self._nprobe, n // 2000))
|
|
84
|
+
probe = np.argsort(-csim, kind="stable")[:nprobe]
|
|
85
|
+
candidates: set[str] = set()
|
|
86
|
+
id_to_row = {nid: i for i, nid in enumerate(self._emb_ids)}
|
|
87
|
+
for c in probe:
|
|
88
|
+
for nid in self._inverted[int(c)]:
|
|
89
|
+
candidates.add(nid)
|
|
90
|
+
if not candidates:
|
|
91
|
+
return super().retrieve(embedding, k)
|
|
92
|
+
|
|
93
|
+
rows = [id_to_row[nid] for nid in candidates if nid in id_to_row]
|
|
94
|
+
sims = self._emb_unit[rows] @ q
|
|
95
|
+
order = np.argsort(-sims, kind="stable")[: min(k, len(rows))]
|
|
96
|
+
chosen = {self._emb_ids[rows[int(i)]] for i in order}
|
|
97
|
+
|
|
98
|
+
edges = [e for e in self._edges.values() if e.is_current and (e.src in chosen or e.dst in chosen)]
|
|
99
|
+
for e in edges:
|
|
100
|
+
chosen.add(e.src)
|
|
101
|
+
chosen.add(e.dst)
|
|
102
|
+
nodes = [self._nodes[n] for n in chosen if n in self._nodes]
|
|
103
|
+
from ..models import Subgraph
|
|
104
|
+
return Subgraph(nodes=nodes, edges=edges)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Production adapters — the real Insight ITS packages behind the ports.
|
|
2
|
+
|
|
3
|
+
Import-guarded so the OSS core stays installable without them (`pip install
|
|
4
|
+
prismcortex[prism]`). Each class wraps exactly one package:
|
|
5
|
+
|
|
6
|
+
- PrismLangProjector → prismlang.PrismProjector (deterministic, CPU-stable vectors)
|
|
7
|
+
- PrismResonanceAdapter → prismresonance.PrismResonance (wavepacket weight + sleep)
|
|
8
|
+
- PrismLibCache → prismlib SQLiteStore (durable cache-as-failover)
|
|
9
|
+
|
|
10
|
+
The bitemporal GraphStore stays PrismCortex-owned (the reference store *is* the design
|
|
11
|
+
— prismrag-patch is a retrieval governor, not a bitemporal database, so it slots in as
|
|
12
|
+
a recall-time filter rather than the store itself). The Chorus/prismlib cluster mesh is
|
|
13
|
+
the remaining seam; InProcessMesh stands in until it's wired.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import time
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PrismLangProjector:
|
|
25
|
+
"""GistProjector via prismlang.PrismProjector.
|
|
26
|
+
|
|
27
|
+
project() returns (category_slug, vector, trace). The vector is all-MiniLM-L6-v2
|
|
28
|
+
JL-reduced with a seed derived from tenant_id — deterministic given a fixed tenant,
|
|
29
|
+
which is exactly the CPU-stable projection the read-path determinism contract needs.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, taxonomy=None, tenant_id: str = "prismcortex", k: int = 128) -> None:
|
|
33
|
+
# k = projection dimension. PrismLang defaults to 64, which crowds at scale
|
|
34
|
+
# (retrieval recall drops to ~0.86 at 3k facts); 128 restores it to ~0.97 with no
|
|
35
|
+
# latency cost. See benchmarks/scale_bench.py.
|
|
36
|
+
from prismlang import Category, PrismProjector, TaxonomyConfig
|
|
37
|
+
|
|
38
|
+
if taxonomy is None:
|
|
39
|
+
taxonomy = TaxonomyConfig(categories=[
|
|
40
|
+
Category("general", "General", ["the", "a", "is", "of", "to"]),
|
|
41
|
+
Category("preference", "Preference", ["like", "prefer", "favorite", "want", "hate"]),
|
|
42
|
+
Category("fact", "Fact", ["is", "are", "has", "budget", "name", "id", "number"]),
|
|
43
|
+
Category("tech", "Tech", ["deploy", "database", "server", "region", "api", "model"]),
|
|
44
|
+
])
|
|
45
|
+
self._p = PrismProjector(taxonomy, tenant_id=tenant_id, k=k)
|
|
46
|
+
_, probe, _ = self._p.project("dimension probe")
|
|
47
|
+
self.dim = len(probe)
|
|
48
|
+
|
|
49
|
+
def embed(self, text: str) -> list[float]:
|
|
50
|
+
_, vec, _ = self._p.project(text)
|
|
51
|
+
return [float(x) for x in vec]
|
|
52
|
+
|
|
53
|
+
def classify(self, text: str) -> str:
|
|
54
|
+
cat, _, _ = self._p.project(text)
|
|
55
|
+
return cat
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class PrismResonanceAdapter:
|
|
59
|
+
"""ResonanceEngine via prismresonance.PrismResonance (compiles an ONNX graph on
|
|
60
|
+
first construction; writes a small state db)."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, embedding_dim: int, state_path: str = "resonance_state.db", onnx_path: str = "resonance_engine.onnx") -> None:
|
|
63
|
+
from prismresonance import PrismResonance
|
|
64
|
+
from prismresonance.frequency import FrequencyFamily
|
|
65
|
+
|
|
66
|
+
self._FF = FrequencyFamily
|
|
67
|
+
self._r = PrismResonance.create(embedding_dim=embedding_dim, state_path=state_path, onnx_path=onnx_path)
|
|
68
|
+
self._amps: dict[str, np.ndarray] = {} # kept so reinforce can re-ingest
|
|
69
|
+
|
|
70
|
+
def _freq(self, band: str):
|
|
71
|
+
return getattr(self._FF, band, self._FF.NEUTRAL)
|
|
72
|
+
|
|
73
|
+
def ingest(self, chunk_id: str, amplitude: list[float], band: str) -> None:
|
|
74
|
+
amp = np.asarray(amplitude, dtype=np.float32)
|
|
75
|
+
if amp.size == 0:
|
|
76
|
+
return
|
|
77
|
+
self._amps[chunk_id] = amp
|
|
78
|
+
self._r.ingest(chunk_id, amp, self._freq(band))
|
|
79
|
+
|
|
80
|
+
def reinforce(self, chunk_id: str) -> None:
|
|
81
|
+
amp = self._amps.get(chunk_id)
|
|
82
|
+
if amp is not None: # re-ingest at higher salience ≈ LTP
|
|
83
|
+
self._r.ingest(chunk_id, amp, self._FF.ALERT)
|
|
84
|
+
|
|
85
|
+
def rank(self, candidate_ids: list[str]) -> list[str]:
|
|
86
|
+
return list(candidate_ids) # resonance ordering is not on the determinism path
|
|
87
|
+
|
|
88
|
+
def consolidate(self) -> None:
|
|
89
|
+
self._r.sleep()
|
|
90
|
+
|
|
91
|
+
def shutdown(self) -> None:
|
|
92
|
+
try:
|
|
93
|
+
self._r.shutdown()
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class PrismLibCache:
|
|
99
|
+
"""ResponseCache via prismlib SQLiteStore — exact-key, durable (cache-as-failover).
|
|
100
|
+
|
|
101
|
+
packet_id carries our content-address; response carries the frozen answer. Durable
|
|
102
|
+
by file path so a frozen answer survives restart and cache eviction.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
_TEN_YEARS = 10 * 365 * 24 * 3600
|
|
106
|
+
|
|
107
|
+
def __init__(self, db_path: str = ".prismcortex_cache/prismlib.db") -> None:
|
|
108
|
+
from prism.cache import CacheEntry, SQLiteStore
|
|
109
|
+
|
|
110
|
+
os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True)
|
|
111
|
+
self._CacheEntry = CacheEntry
|
|
112
|
+
self._db_path = db_path
|
|
113
|
+
self._store = SQLiteStore(db_path=db_path)
|
|
114
|
+
|
|
115
|
+
def get(self, key: str) -> Optional[str]:
|
|
116
|
+
entry = self._store.load(key)
|
|
117
|
+
return entry.response if entry else None
|
|
118
|
+
|
|
119
|
+
def has(self, key: str) -> bool:
|
|
120
|
+
return self._store.load(key) is not None
|
|
121
|
+
|
|
122
|
+
def put(self, key: str, value: str) -> None:
|
|
123
|
+
now = time.time()
|
|
124
|
+
self._store.save(self._CacheEntry(
|
|
125
|
+
packet_id=key, query_text="", response=value,
|
|
126
|
+
created_at=now, expires_at=now + self._TEN_YEARS,
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
def clear(self) -> None:
|
|
130
|
+
"""Drop all cached answers (recreate the store) — used on erasure."""
|
|
131
|
+
from prism.cache import SQLiteStore
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
self._store.close()
|
|
135
|
+
except Exception: # noqa: BLE001
|
|
136
|
+
pass
|
|
137
|
+
if self._db_path != ":memory:" and os.path.exists(self._db_path):
|
|
138
|
+
os.remove(self._db_path)
|
|
139
|
+
self._store = SQLiteStore(db_path=self._db_path)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def prism_memory(
|
|
143
|
+
*,
|
|
144
|
+
model: Optional[str] = None,
|
|
145
|
+
cache_db: str = ".prismcortex_cache/prismlib.db",
|
|
146
|
+
tenant_id: str = "prismcortex",
|
|
147
|
+
resonance_state: str = ".prismcortex_cache/resonance_state.db",
|
|
148
|
+
resonance_onnx: str = ".prismcortex_cache/resonance_engine.onnx",
|
|
149
|
+
k: int = 8,
|
|
150
|
+
):
|
|
151
|
+
"""Production-wired Memory: real PrismLang + PrismResonance + PrismLib + Gemini.
|
|
152
|
+
|
|
153
|
+
GraphStore + Mesh remain the reference (PrismCortex-owned bitemporal store; mesh is
|
|
154
|
+
the open Chorus seam). Needs prismcortex[prism], prismcortex[gemini], and a key.
|
|
155
|
+
"""
|
|
156
|
+
from ..engine import Memory
|
|
157
|
+
from ..llm.gemini import GeminiClient
|
|
158
|
+
from .reference import InMemoryGraphStore, InProcessMesh, ListStaging
|
|
159
|
+
|
|
160
|
+
projector = PrismLangProjector(tenant_id=tenant_id)
|
|
161
|
+
resonance = PrismResonanceAdapter(embedding_dim=projector.dim, state_path=resonance_state, onnx_path=resonance_onnx)
|
|
162
|
+
cache = PrismLibCache(db_path=cache_db)
|
|
163
|
+
gemini = GeminiClient(model=model)
|
|
164
|
+
return Memory(
|
|
165
|
+
projector=projector,
|
|
166
|
+
extractor=gemini,
|
|
167
|
+
renderer=gemini,
|
|
168
|
+
store=InMemoryGraphStore(),
|
|
169
|
+
resonance=resonance,
|
|
170
|
+
cache=cache,
|
|
171
|
+
mesh=InProcessMesh(),
|
|
172
|
+
staging=ListStaging(),
|
|
173
|
+
k=k,
|
|
174
|
+
)
|