cortex-vault 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.
- cortex_memory/__init__.py +33 -0
- cortex_memory/__main__.py +65 -0
- cortex_memory/agent.py +225 -0
- cortex_memory/config.py +112 -0
- cortex_memory/graph/__init__.py +4 -0
- cortex_memory/graph/memory_graph.py +87 -0
- cortex_memory/ingestion/__init__.py +14 -0
- cortex_memory/ingestion/ingestor.py +226 -0
- cortex_memory/llm/__init__.py +4 -0
- cortex_memory/llm/chat.py +84 -0
- cortex_memory/prompts/__init__.py +4 -0
- cortex_memory/prompts/templates.py +59 -0
- cortex_memory/retrieval/__init__.py +4 -0
- cortex_memory/retrieval/retriever.py +196 -0
- cortex_vault-0.1.0.dist-info/METADATA +231 -0
- cortex_vault-0.1.0.dist-info/RECORD +19 -0
- cortex_vault-0.1.0.dist-info/WHEEL +5 -0
- cortex_vault-0.1.0.dist-info/entry_points.txt +2 -0
- cortex_vault-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory
|
|
3
|
+
~~~~~~~~~~~~~
|
|
4
|
+
An LLM-powered memory layer backed by a Neo4j knowledge graph.
|
|
5
|
+
|
|
6
|
+
Public API::
|
|
7
|
+
|
|
8
|
+
from cortex_memory import CortexMemory, CortexConfig
|
|
9
|
+
|
|
10
|
+
# Quickstart ā reads NEO4J_* and GROQ_API_KEY from .env
|
|
11
|
+
memory = CortexMemory.from_env()
|
|
12
|
+
response = memory.chat("What do I know about Japan?")
|
|
13
|
+
memory.ingest("User is planning a trip to Kyoto in October.")
|
|
14
|
+
memory.close()
|
|
15
|
+
|
|
16
|
+
Low-level access::
|
|
17
|
+
|
|
18
|
+
from cortex_memory import WeightedMemoryRetriever, ingest_text_to_memory_graph
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from cortex_memory.agent import CortexMemory
|
|
22
|
+
from cortex_memory.config import CortexConfig
|
|
23
|
+
from cortex_memory.ingestion.ingestor import ingest_text_to_memory_graph
|
|
24
|
+
from cortex_memory.retrieval.retriever import WeightedMemoryRetriever
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"CortexMemory",
|
|
28
|
+
"CortexConfig",
|
|
29
|
+
"ingest_text_to_memory_graph",
|
|
30
|
+
"WeightedMemoryRetriever",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.__main__
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
CLI entrypoint ā invoked via ``python -m cortex_memory`` or the
|
|
5
|
+
``cortex-memory`` script installed by pyproject.toml.
|
|
6
|
+
|
|
7
|
+
Usage
|
|
8
|
+
-----
|
|
9
|
+
cortex-memory chat "What do I know about Japan?"
|
|
10
|
+
cortex-memory ingest "User loves hiking in the Alps."
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from cortex_memory import CortexMemory
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
parser = argparse.ArgumentParser(
|
|
23
|
+
prog="cortex-vault",
|
|
24
|
+
description="Cortex Vault CLI ā interact with your Neo4j memory graph.",
|
|
25
|
+
)
|
|
26
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
27
|
+
|
|
28
|
+
# --- chat ---
|
|
29
|
+
chat_parser = sub.add_parser("chat", help="Query the memory agent.")
|
|
30
|
+
chat_parser.add_argument("query", help="The query to send to the assistant.")
|
|
31
|
+
chat_parser.add_argument(
|
|
32
|
+
"--top-k", type=int, default=None, help="Number of memories to retrieve."
|
|
33
|
+
)
|
|
34
|
+
chat_parser.add_argument(
|
|
35
|
+
"--no-ingest", action="store_true", help="Disable auto-ingestion of the query."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# --- ingest ---
|
|
39
|
+
ingest_parser = sub.add_parser("ingest", help="Ingest text into memory.")
|
|
40
|
+
ingest_parser.add_argument("text", help="Text to evaluate and store.")
|
|
41
|
+
ingest_parser.add_argument(
|
|
42
|
+
"--source", default="cli", help="Source tag for the memory (default: 'cli')."
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
args = parser.parse_args()
|
|
46
|
+
|
|
47
|
+
with CortexMemory.from_env() as memory:
|
|
48
|
+
if args.command == "chat":
|
|
49
|
+
response = memory.chat(
|
|
50
|
+
args.query,
|
|
51
|
+
top_k=args.top_k,
|
|
52
|
+
auto_ingest=not args.no_ingest,
|
|
53
|
+
)
|
|
54
|
+
print(f"\nš¤ Assistant:\n{response}\n")
|
|
55
|
+
|
|
56
|
+
elif args.command == "ingest":
|
|
57
|
+
ingested = memory.ingest(args.text, source=args.source)
|
|
58
|
+
if ingested:
|
|
59
|
+
print("ā
Memory ingested successfully.")
|
|
60
|
+
else:
|
|
61
|
+
print("ā No ingestion was performed.")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
cortex_memory/agent.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.agent
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
``CortexMemory`` ā the single public class that orchestrates the full
|
|
5
|
+
retrieval-augmented memory pipeline.
|
|
6
|
+
|
|
7
|
+
Typical usage::
|
|
8
|
+
|
|
9
|
+
from cortex_memory import CortexMemory
|
|
10
|
+
|
|
11
|
+
memory = CortexMemory.from_env()
|
|
12
|
+
response = memory.chat("What do I know about my Japan trip?")
|
|
13
|
+
memory.ingest("User booked flights to Tokyo for next March.")
|
|
14
|
+
memory.close()
|
|
15
|
+
|
|
16
|
+
Or as a context manager::
|
|
17
|
+
|
|
18
|
+
with CortexMemory.from_env() as memory:
|
|
19
|
+
response = memory.chat("Remind me about my diet goals.")
|
|
20
|
+
memory.ingest("User has decided to go vegan starting Monday.")
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import TYPE_CHECKING
|
|
26
|
+
|
|
27
|
+
from sentence_transformers import SentenceTransformer
|
|
28
|
+
|
|
29
|
+
from cortex_memory.config import CortexConfig
|
|
30
|
+
from cortex_memory.ingestion.ingestor import ingest_text_to_memory_graph
|
|
31
|
+
from cortex_memory.llm.chat import build_llm_client, chat_with_assistant
|
|
32
|
+
from cortex_memory.retrieval.retriever import WeightedMemoryRetriever
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CortexMemory:
|
|
36
|
+
"""
|
|
37
|
+
High-level orchestrator for the Cortex Memory system.
|
|
38
|
+
|
|
39
|
+
Manages the lifecycle of the LLM client, embedder, and Neo4j
|
|
40
|
+
retriever so callers don't need to wire them up manually.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
config : CortexConfig
|
|
45
|
+
Runtime configuration. Build one with ``CortexConfig.from_env()``
|
|
46
|
+
or pass values explicitly.
|
|
47
|
+
embedder : SentenceTransformer | None
|
|
48
|
+
Optional pre-loaded embedding model. If ``None``, one is
|
|
49
|
+
instantiated from ``config.embedder_model``.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
config: CortexConfig,
|
|
55
|
+
embedder: SentenceTransformer | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
self.config = config
|
|
58
|
+
|
|
59
|
+
# --- shared embedder (loaded once) ---
|
|
60
|
+
self._embedder: SentenceTransformer = (
|
|
61
|
+
embedder
|
|
62
|
+
if embedder is not None
|
|
63
|
+
else SentenceTransformer(config.embedder_model)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# --- LLM client ---
|
|
67
|
+
self._llm = build_llm_client(config)
|
|
68
|
+
|
|
69
|
+
# --- Neo4j retriever ---
|
|
70
|
+
self._retriever = WeightedMemoryRetriever(
|
|
71
|
+
config=config, embedder=self._embedder
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# ------------------------------------------------------------------
|
|
75
|
+
# Factories
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_env(
|
|
80
|
+
cls,
|
|
81
|
+
dotenv_path: str | None = None,
|
|
82
|
+
embedder: SentenceTransformer | None = None,
|
|
83
|
+
) -> "CortexMemory":
|
|
84
|
+
"""
|
|
85
|
+
Convenience factory that reads configuration from environment
|
|
86
|
+
variables (and an optional ``.env`` file).
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
dotenv_path : str | None
|
|
91
|
+
Path to a custom ``.env`` file. Defaults to the project root.
|
|
92
|
+
embedder : SentenceTransformer | None
|
|
93
|
+
Bring-your-own embedder (useful when you already have one
|
|
94
|
+
loaded in the parent application).
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
CortexMemory
|
|
99
|
+
"""
|
|
100
|
+
config = CortexConfig.from_env(dotenv_path=dotenv_path)
|
|
101
|
+
return cls(config=config, embedder=embedder)
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------
|
|
104
|
+
# Core API
|
|
105
|
+
# ------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
def chat(
|
|
108
|
+
self,
|
|
109
|
+
user_input: str,
|
|
110
|
+
*,
|
|
111
|
+
chat_history: list[dict] | None = None,
|
|
112
|
+
top_k: int | None = None,
|
|
113
|
+
auto_ingest: bool = True,
|
|
114
|
+
) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Retrieve relevant memories, generate an LLM response, optionally
|
|
117
|
+
ingest the user's message, and reinforce retrieved memories.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
user_input : str
|
|
122
|
+
The user's message.
|
|
123
|
+
chat_history : list[dict] | None
|
|
124
|
+
Prior conversation turns for multi-turn context.
|
|
125
|
+
top_k : int | None
|
|
126
|
+
Override the number of memories to retrieve for this call.
|
|
127
|
+
auto_ingest : bool
|
|
128
|
+
Whether to automatically attempt to ingest the user's message
|
|
129
|
+
after generating a response (default: ``True``).
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
str
|
|
134
|
+
The assistant's response.
|
|
135
|
+
"""
|
|
136
|
+
# 1. Retrieve
|
|
137
|
+
results = self._retriever.search_weighted_memories(user_input, top_k=top_k)
|
|
138
|
+
memories = [r["content"] for r in results]
|
|
139
|
+
|
|
140
|
+
# 2. Generate response
|
|
141
|
+
response = chat_with_assistant(
|
|
142
|
+
user_input,
|
|
143
|
+
self._llm,
|
|
144
|
+
chat_history=chat_history,
|
|
145
|
+
memories=memories,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# 3. Optionally ingest the query
|
|
149
|
+
if auto_ingest:
|
|
150
|
+
self.ingest(user_input, source="assistant_chat")
|
|
151
|
+
|
|
152
|
+
# 4. Reinforce retrieved memories
|
|
153
|
+
for result in results:
|
|
154
|
+
self._retriever.reinforce_memory(result["label"])
|
|
155
|
+
|
|
156
|
+
return response
|
|
157
|
+
|
|
158
|
+
def ingest(
|
|
159
|
+
self,
|
|
160
|
+
text: str,
|
|
161
|
+
*,
|
|
162
|
+
source: str = "upload",
|
|
163
|
+
tags: list[str] | None = None,
|
|
164
|
+
) -> bool:
|
|
165
|
+
"""
|
|
166
|
+
Evaluate *text* and persist it to the memory graph if worthy.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
text : str
|
|
171
|
+
Raw text to evaluate.
|
|
172
|
+
source : str
|
|
173
|
+
Provenance tag (e.g. ``"upload"``, ``"assistant_chat"``).
|
|
174
|
+
tags : list[str] | None
|
|
175
|
+
Additional keyword tags to attach to the stored memory nodes.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
bool
|
|
180
|
+
``True`` if at least one chunk was ingested.
|
|
181
|
+
"""
|
|
182
|
+
return ingest_text_to_memory_graph(
|
|
183
|
+
text,
|
|
184
|
+
config=self.config,
|
|
185
|
+
llm=self._llm,
|
|
186
|
+
embedder=self._embedder,
|
|
187
|
+
source=source,
|
|
188
|
+
tags=tags,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def retrieve(
|
|
192
|
+
self,
|
|
193
|
+
query: str,
|
|
194
|
+
top_k: int | None = None,
|
|
195
|
+
) -> list[dict]:
|
|
196
|
+
"""
|
|
197
|
+
Perform a raw weighted memory search without chat or ingestion.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
query : str
|
|
202
|
+
The search query.
|
|
203
|
+
top_k : int | None
|
|
204
|
+
Number of results to retrieve.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
list[dict]
|
|
209
|
+
Memory records sorted by weighted score.
|
|
210
|
+
"""
|
|
211
|
+
return self._retriever.search_weighted_memories(query, top_k=top_k)
|
|
212
|
+
|
|
213
|
+
# ------------------------------------------------------------------
|
|
214
|
+
# Lifecycle
|
|
215
|
+
# ------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
def close(self) -> None:
|
|
218
|
+
"""Release Neo4j driver connections."""
|
|
219
|
+
self._retriever.close()
|
|
220
|
+
|
|
221
|
+
def __enter__(self) -> "CortexMemory":
|
|
222
|
+
return self
|
|
223
|
+
|
|
224
|
+
def __exit__(self, *_) -> None:
|
|
225
|
+
self.close()
|
cortex_memory/config.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.config
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Centralised configuration for the Cortex Memory package.
|
|
5
|
+
|
|
6
|
+
All runtime parameters live here so no individual module ever
|
|
7
|
+
calls os.getenv() directly. Construct with CortexConfig.from_env()
|
|
8
|
+
to pull values from a .env file, or pass them explicitly.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CortexConfig:
|
|
20
|
+
"""
|
|
21
|
+
Holds every tunable parameter for the Cortex Memory system.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
neo4j_uri : str
|
|
26
|
+
Bolt URI for the Neo4j instance (e.g. ``bolt://localhost:7687``).
|
|
27
|
+
neo4j_username : str
|
|
28
|
+
Neo4j username (default: ``"neo4j"``).
|
|
29
|
+
neo4j_password : str
|
|
30
|
+
Neo4j password.
|
|
31
|
+
groq_api_key : str
|
|
32
|
+
API key for Groq.
|
|
33
|
+
model : str
|
|
34
|
+
LLM model name served by Groq (default: ``"openai/gpt-oss-20b"``).
|
|
35
|
+
embedder_model : str
|
|
36
|
+
SentenceTransformer model name (default: ``"all-MiniLM-L6-v2"``).
|
|
37
|
+
top_k : int
|
|
38
|
+
Number of candidate memories fetched from the vector index
|
|
39
|
+
before weighted re-ranking (default: 5).
|
|
40
|
+
score_threshold : float
|
|
41
|
+
Minimum weighted score a memory must achieve to be returned
|
|
42
|
+
(default: 0.65).
|
|
43
|
+
chunk_max_chars : int
|
|
44
|
+
Maximum character width for text chunking (default: 400).
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
neo4j_uri: str
|
|
48
|
+
neo4j_username: str
|
|
49
|
+
neo4j_password: str
|
|
50
|
+
groq_api_key: str
|
|
51
|
+
|
|
52
|
+
# --- LLM ---
|
|
53
|
+
model: str = "openai/gpt-oss-20b"
|
|
54
|
+
|
|
55
|
+
# --- Embedder ---
|
|
56
|
+
embedder_model: str = "all-MiniLM-L6-v2"
|
|
57
|
+
|
|
58
|
+
# --- Retrieval ---
|
|
59
|
+
top_k: int = 5
|
|
60
|
+
score_threshold: float = 0.65
|
|
61
|
+
|
|
62
|
+
# --- Ingestion ---
|
|
63
|
+
chunk_max_chars: int = 400
|
|
64
|
+
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
# Factory
|
|
67
|
+
# ------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_env(cls, dotenv_path: str | None = None) -> "CortexConfig":
|
|
71
|
+
"""
|
|
72
|
+
Build a ``CortexConfig`` from environment variables.
|
|
73
|
+
|
|
74
|
+
Loads a ``.env`` file if present (or from *dotenv_path* if given).
|
|
75
|
+
Required env vars:
|
|
76
|
+
- ``NEO4J_URI``
|
|
77
|
+
- ``NEO4J_PASSWORD``
|
|
78
|
+
- ``GROQ_API_KEY``
|
|
79
|
+
|
|
80
|
+
Optional env vars (have sensible defaults):
|
|
81
|
+
- ``NEO4J_USERNAME`` (default ``"neo4j"``)
|
|
82
|
+
- ``CORTEX_MODEL``
|
|
83
|
+
- ``CORTEX_EMBEDDER``
|
|
84
|
+
- ``CORTEX_TOP_K``
|
|
85
|
+
- ``CORTEX_SCORE_THRESHOLD``
|
|
86
|
+
- ``CORTEX_CHUNK_MAX_CHARS``
|
|
87
|
+
"""
|
|
88
|
+
load_dotenv(dotenv_path=dotenv_path)
|
|
89
|
+
|
|
90
|
+
neo4j_uri = os.getenv("NEO4J_URI", "")
|
|
91
|
+
neo4j_username = os.getenv("NEO4J_USERNAME", "neo4j")
|
|
92
|
+
neo4j_password = os.getenv("NEO4J_PASSWORD", "")
|
|
93
|
+
groq_api_key = os.getenv("GROQ_API_KEY", "")
|
|
94
|
+
|
|
95
|
+
if not neo4j_uri:
|
|
96
|
+
raise EnvironmentError("NEO4J_URI is not set in environment.")
|
|
97
|
+
if not neo4j_password:
|
|
98
|
+
raise EnvironmentError("NEO4J_PASSWORD is not set in environment.")
|
|
99
|
+
if not groq_api_key:
|
|
100
|
+
raise EnvironmentError("GROQ_API_KEY is not set in environment.")
|
|
101
|
+
|
|
102
|
+
return cls(
|
|
103
|
+
neo4j_uri=neo4j_uri,
|
|
104
|
+
neo4j_username=neo4j_username,
|
|
105
|
+
neo4j_password=neo4j_password,
|
|
106
|
+
groq_api_key=groq_api_key,
|
|
107
|
+
model=os.getenv("CORTEX_MODEL", "openai/gpt-oss-20b"),
|
|
108
|
+
embedder_model=os.getenv("CORTEX_EMBEDDER", "all-MiniLM-L6-v2"),
|
|
109
|
+
top_k=int(os.getenv("CORTEX_TOP_K", "5")),
|
|
110
|
+
score_threshold=float(os.getenv("CORTEX_SCORE_THRESHOLD", "0.65")),
|
|
111
|
+
chunk_max_chars=int(os.getenv("CORTEX_CHUNK_MAX_CHARS", "400")),
|
|
112
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cortex_memory.graph.memory_graph
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
Low-level Neo4j persistence layer.
|
|
5
|
+
|
|
6
|
+
``MemoryGraph`` is the only class that knows about Cypher ā every
|
|
7
|
+
other module calls its public methods rather than writing queries
|
|
8
|
+
directly.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from neo4j import GraphDatabase
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MemoryGraph:
|
|
17
|
+
"""
|
|
18
|
+
Thin wrapper around the Neo4j driver that handles upsert operations
|
|
19
|
+
for ``Memory`` and ``Entity`` nodes.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
uri : str
|
|
24
|
+
Bolt URI for the Neo4j instance.
|
|
25
|
+
username : str
|
|
26
|
+
Neo4j username.
|
|
27
|
+
password : str
|
|
28
|
+
Neo4j password.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, uri: str, username: str, password: str) -> None:
|
|
32
|
+
self.driver = GraphDatabase.driver(uri, auth=(username, password))
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------------------
|
|
35
|
+
# Write operations
|
|
36
|
+
# ------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
def upsert_memory(self, memory: dict) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Merge a ``Memory`` node by ``id`` and set all scalar properties.
|
|
41
|
+
Each name in ``memory["related_entities"]`` is merged as an
|
|
42
|
+
``Entity`` node linked via ``:RELATED_TO``.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
memory : dict
|
|
47
|
+
Dictionary with keys: id, type, label, content, source,
|
|
48
|
+
created_at, last_accessed, importance, stability, status,
|
|
49
|
+
tags, embedding, related_entities.
|
|
50
|
+
"""
|
|
51
|
+
query = """
|
|
52
|
+
MERGE (m:Memory {id: $id})
|
|
53
|
+
SET m += {
|
|
54
|
+
type: $type,
|
|
55
|
+
label: $label,
|
|
56
|
+
content: $content,
|
|
57
|
+
source: $source,
|
|
58
|
+
created_at: datetime($created_at),
|
|
59
|
+
last_accessed: datetime($last_accessed),
|
|
60
|
+
importance: $importance,
|
|
61
|
+
stability: $stability,
|
|
62
|
+
status: $status,
|
|
63
|
+
tags: $tags,
|
|
64
|
+
embedding: $embedding
|
|
65
|
+
}
|
|
66
|
+
WITH m
|
|
67
|
+
UNWIND $related_entities AS entity
|
|
68
|
+
MERGE (e:Entity {name: entity})
|
|
69
|
+
MERGE (m)-[:RELATED_TO]->(e)
|
|
70
|
+
RETURN m.id AS id
|
|
71
|
+
"""
|
|
72
|
+
with self.driver.session() as session:
|
|
73
|
+
session.run(query, **memory)
|
|
74
|
+
|
|
75
|
+
# ------------------------------------------------------------------
|
|
76
|
+
# Lifecycle
|
|
77
|
+
# ------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
def close(self) -> None:
|
|
80
|
+
"""Close the underlying Neo4j driver connection."""
|
|
81
|
+
self.driver.close()
|
|
82
|
+
|
|
83
|
+
def __enter__(self) -> "MemoryGraph":
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def __exit__(self, *_) -> None:
|
|
87
|
+
self.close()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""cortex_memory.ingestion package."""
|
|
2
|
+
from cortex_memory.ingestion.ingestor import (
|
|
3
|
+
ingest_text_to_memory_graph,
|
|
4
|
+
chunk_text,
|
|
5
|
+
extract_query_tags,
|
|
6
|
+
get_all_entities,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ingest_text_to_memory_graph",
|
|
11
|
+
"chunk_text",
|
|
12
|
+
"extract_query_tags",
|
|
13
|
+
"get_all_entities",
|
|
14
|
+
]
|