cortexlayer 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cortexlayer/__init__.py +61 -0
- cortexlayer/_engine/__init__.py +0 -0
- cortexlayer/_engine/compression.py +87 -0
- cortexlayer/_engine/facts/__init__.py +1 -0
- cortexlayer/_engine/facts/additive_extraction_prompt.txt +476 -0
- cortexlayer/_engine/facts/backends.py +196 -0
- cortexlayer/_engine/facts/engine.py +556 -0
- cortexlayer/_engine/facts/entities.py +796 -0
- cortexlayer/_engine/facts/prompts.py +100 -0
- cortexlayer/_engine/facts/scoring.py +148 -0
- cortexlayer/_engine/ingestion.py +84 -0
- cortexlayer/_engine/linking.py +68 -0
- cortexlayer/_engine/nlp.py +162 -0
- cortexlayer/_engine/retrieval.py +55 -0
- cortexlayer/_engine/shaping.py +96 -0
- cortexlayer/_engine/storage.py +295 -0
- cortexlayer/_validate.py +23 -0
- cortexlayer/_version.py +1 -0
- cortexlayer/client.py +526 -0
- cortexlayer/errors.py +108 -0
- cortexlayer/memory.py +372 -0
- cortexlayer/py.typed +0 -0
- cortexlayer/types.py +199 -0
- cortexlayer-0.1.0.dist-info/METADATA +266 -0
- cortexlayer-0.1.0.dist-info/RECORD +29 -0
- cortexlayer-0.1.0.dist-info/WHEEL +5 -0
- cortexlayer-0.1.0.dist-info/licenses/LICENSE +202 -0
- cortexlayer-0.1.0.dist-info/licenses/NOTICE +40 -0
- cortexlayer-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Pluggable LLM and embedder for fact memory.
|
|
2
|
+
|
|
3
|
+
Defaults mirror the Cortex server's Mem0 configuration (Ollama, ``think`` off,
|
|
4
|
+
temperature/top_p/num_predict as Mem0's Ollama client: 0.1 / 0.1 / 2000) so the
|
|
5
|
+
extraction call is the same call. Anything with the same method works, so tests
|
|
6
|
+
and other providers need no Ollama.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Callable, List, Optional, Protocol, Sequence
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from ...errors import CortexConfigError, LLMError
|
|
17
|
+
|
|
18
|
+
DEFAULT_OLLAMA_HOST = "http://localhost:11434"
|
|
19
|
+
DEFAULT_LLM_MODEL = "qwen3.5:9b"
|
|
20
|
+
DEFAULT_OLLAMA_EMBED_MODEL = "qwen3-embedding:8b"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def ollama_host() -> str:
|
|
24
|
+
return os.environ.get("OLLAMA_HOST", DEFAULT_OLLAMA_HOST).rstrip("/")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LLM(Protocol):
|
|
28
|
+
def generate(self, system: str, user: str) -> str:
|
|
29
|
+
"""Return the model's raw reply (expected to be a JSON object string)."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Embedder(Protocol):
|
|
34
|
+
name: str # recorded on the store: vectors from different embedders can't be mixed
|
|
35
|
+
|
|
36
|
+
def embed_batch(self, texts: Sequence[str], action: str = "add") -> List[List[float]]:
|
|
37
|
+
"""One vector per text. ``action`` is ``add`` | ``search`` | ``update``."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class OllamaLLM:
|
|
42
|
+
"""Chat model over Ollama's ``/api/chat`` with native JSON output."""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
model: str = DEFAULT_LLM_MODEL,
|
|
47
|
+
host: Optional[str] = None,
|
|
48
|
+
*,
|
|
49
|
+
temperature: float = 0.1,
|
|
50
|
+
top_p: float = 0.1,
|
|
51
|
+
max_tokens: int = 2000,
|
|
52
|
+
think: bool = False,
|
|
53
|
+
timeout: float = 300.0,
|
|
54
|
+
http_client: Optional[httpx.Client] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
self.model = model
|
|
57
|
+
self.host = (host or ollama_host()).rstrip("/")
|
|
58
|
+
self._opts = {"temperature": temperature, "top_p": top_p, "num_predict": max_tokens}
|
|
59
|
+
self._think = think
|
|
60
|
+
self._timeout = timeout
|
|
61
|
+
self._http = http_client
|
|
62
|
+
|
|
63
|
+
def generate(self, system: str, user: str) -> str:
|
|
64
|
+
body = {
|
|
65
|
+
"model": self.model,
|
|
66
|
+
"messages": [
|
|
67
|
+
{"role": "system", "content": system},
|
|
68
|
+
# Same nudge Mem0's Ollama client appends for JSON mode.
|
|
69
|
+
{"role": "user", "content": user + "\n\nPlease respond with valid JSON only."},
|
|
70
|
+
],
|
|
71
|
+
"format": "json",
|
|
72
|
+
"stream": False,
|
|
73
|
+
# Thinking models otherwise return a trace and JSON extraction
|
|
74
|
+
# silently yields nothing (verified with qwen3.5:9b).
|
|
75
|
+
"think": self._think,
|
|
76
|
+
"options": self._opts,
|
|
77
|
+
}
|
|
78
|
+
try:
|
|
79
|
+
client = self._http or httpx.Client(timeout=self._timeout)
|
|
80
|
+
try:
|
|
81
|
+
resp = client.post(f"{self.host}/api/chat", json=body)
|
|
82
|
+
finally:
|
|
83
|
+
if self._http is None:
|
|
84
|
+
client.close()
|
|
85
|
+
resp.raise_for_status()
|
|
86
|
+
return resp.json()["message"]["content"]
|
|
87
|
+
except (httpx.HTTPError, KeyError, ValueError) as e:
|
|
88
|
+
raise LLMError(f"LLM call to {self.host} ({self.model}) failed: {e}") from e
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class OllamaEmbedder:
|
|
92
|
+
"""Embeddings over Ollama's ``/api/embed``."""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
model: str = DEFAULT_OLLAMA_EMBED_MODEL,
|
|
97
|
+
host: Optional[str] = None,
|
|
98
|
+
*,
|
|
99
|
+
timeout: float = 120.0,
|
|
100
|
+
http_client: Optional[httpx.Client] = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
self.model = model
|
|
103
|
+
self.host = (host or ollama_host()).rstrip("/")
|
|
104
|
+
self.name = f"ollama:{model}"
|
|
105
|
+
self._timeout = timeout
|
|
106
|
+
self._http = http_client
|
|
107
|
+
|
|
108
|
+
def embed_batch(self, texts: Sequence[str], action: str = "add") -> List[List[float]]:
|
|
109
|
+
if not texts:
|
|
110
|
+
return []
|
|
111
|
+
try:
|
|
112
|
+
client = self._http or httpx.Client(timeout=self._timeout)
|
|
113
|
+
try:
|
|
114
|
+
resp = client.post(
|
|
115
|
+
f"{self.host}/api/embed", json={"model": self.model, "input": list(texts)}
|
|
116
|
+
)
|
|
117
|
+
finally:
|
|
118
|
+
if self._http is None:
|
|
119
|
+
client.close()
|
|
120
|
+
resp.raise_for_status()
|
|
121
|
+
vectors = resp.json().get("embeddings") or []
|
|
122
|
+
except (httpx.HTTPError, ValueError) as e:
|
|
123
|
+
raise LLMError(f"Embedding call to {self.host} ({self.model}) failed: {e}") from e
|
|
124
|
+
if len(vectors) != len(texts):
|
|
125
|
+
raise LLMError(
|
|
126
|
+
f"Ollama returned {len(vectors)} embeddings for {len(texts)} texts ({self.model})"
|
|
127
|
+
)
|
|
128
|
+
return vectors
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ChromaEmbedder:
|
|
132
|
+
"""Chroma's built-in ONNX MiniLM-L6-v2 — no Ollama or network needed after
|
|
133
|
+
the one-time ~80 MB model download. The library's default embedder."""
|
|
134
|
+
|
|
135
|
+
name = "chroma:onnx-minilm-l6-v2"
|
|
136
|
+
|
|
137
|
+
def __init__(self) -> None:
|
|
138
|
+
self._fn: Any = None
|
|
139
|
+
|
|
140
|
+
def embed_batch(self, texts: Sequence[str], action: str = "add") -> List[List[float]]:
|
|
141
|
+
if not texts:
|
|
142
|
+
return []
|
|
143
|
+
if self._fn is None:
|
|
144
|
+
from chromadb.utils.embedding_functions import DefaultEmbeddingFunction
|
|
145
|
+
|
|
146
|
+
self._fn = DefaultEmbeddingFunction()
|
|
147
|
+
return [[float(x) for x in v] for v in self._fn(list(texts))]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class _CallableLLM:
|
|
151
|
+
def __init__(self, fn: Callable[[str, str], str]) -> None:
|
|
152
|
+
self._fn = fn
|
|
153
|
+
|
|
154
|
+
def generate(self, system: str, user: str) -> str:
|
|
155
|
+
return self._fn(system, user)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def resolve_llm(spec: Any) -> LLM:
|
|
159
|
+
"""``None`` → default Ollama; dict → ``OllamaLLM(**dict)``; an object with
|
|
160
|
+
``generate`` → itself; a callable ``fn(system, user) -> str`` → wrapped."""
|
|
161
|
+
if spec is None:
|
|
162
|
+
return OllamaLLM()
|
|
163
|
+
if isinstance(spec, dict):
|
|
164
|
+
cfg = dict(spec)
|
|
165
|
+
provider = cfg.pop("provider", "ollama")
|
|
166
|
+
if provider != "ollama":
|
|
167
|
+
raise CortexConfigError(f"unknown llm provider {provider!r} (only 'ollama' is built in)")
|
|
168
|
+
return OllamaLLM(**cfg)
|
|
169
|
+
if hasattr(spec, "generate"):
|
|
170
|
+
return spec
|
|
171
|
+
if callable(spec):
|
|
172
|
+
return _CallableLLM(spec)
|
|
173
|
+
raise CortexConfigError(f"llm must be None, a dict, a callable or an object with generate(); got {spec!r}")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def resolve_embedder(spec: Any) -> Embedder:
|
|
177
|
+
"""``None``/``"chroma"`` → Chroma ONNX; ``"ollama"`` → Ollama default model;
|
|
178
|
+
dict → ``{"provider": "ollama"|"chroma", ...}``; an object with
|
|
179
|
+
``embed_batch`` and ``name`` → itself."""
|
|
180
|
+
if spec is None or spec == "chroma":
|
|
181
|
+
return ChromaEmbedder()
|
|
182
|
+
if spec == "ollama":
|
|
183
|
+
return OllamaEmbedder()
|
|
184
|
+
if isinstance(spec, dict):
|
|
185
|
+
cfg = dict(spec)
|
|
186
|
+
provider = cfg.pop("provider", "ollama")
|
|
187
|
+
if provider == "ollama":
|
|
188
|
+
return OllamaEmbedder(**cfg)
|
|
189
|
+
if provider == "chroma":
|
|
190
|
+
return ChromaEmbedder()
|
|
191
|
+
raise CortexConfigError(f"unknown embedder provider {provider!r}")
|
|
192
|
+
if hasattr(spec, "embed_batch") and hasattr(spec, "name"):
|
|
193
|
+
return spec
|
|
194
|
+
raise CortexConfigError(
|
|
195
|
+
f"embedder must be None, 'chroma', 'ollama', a dict, or an object with embed_batch() and name; got {spec!r}"
|
|
196
|
+
)
|