aimemoryos 0.1.1__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.
- aimemoryos-0.1.1/PKG-INFO +159 -0
- aimemoryos-0.1.1/README.md +136 -0
- aimemoryos-0.1.1/aimemoryos/__init__.py +3 -0
- aimemoryos-0.1.1/aimemoryos/agents/__init__.py +7 -0
- aimemoryos-0.1.1/aimemoryos/agents/context_builder.py +171 -0
- aimemoryos-0.1.1/aimemoryos/agents/memory_client.py +438 -0
- aimemoryos-0.1.1/aimemoryos/agents/session_manager.py +273 -0
- aimemoryos-0.1.1/aimemoryos/core/__init__.py +1 -0
- aimemoryos-0.1.1/aimemoryos/core/config.py +39 -0
- aimemoryos-0.1.1/aimemoryos/core/exceptions.py +95 -0
- aimemoryos-0.1.1/aimemoryos/core/interfaces.py +287 -0
- aimemoryos-0.1.1/aimemoryos/core/runtime.py +125 -0
- aimemoryos-0.1.1/aimemoryos/graph/__init__.py +2 -0
- aimemoryos-0.1.1/aimemoryos/graph/contradiction_detector.py +519 -0
- aimemoryos-0.1.1/aimemoryos/graph/ontology.py +477 -0
- aimemoryos-0.1.1/aimemoryos/graph/traversal.py +214 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/__init__.py +2 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/chunker.py +286 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/deduplicator.py +134 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/multimodal_router.py +26 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/pdf_loader.py +31 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/pipeline.py +255 -0
- aimemoryos-0.1.1/aimemoryos/ingestion/preprocessor.py +24 -0
- aimemoryos-0.1.1/aimemoryos/main.py +87 -0
- aimemoryos-0.1.1/aimemoryos/memory/episodic.py +36 -0
- aimemoryos-0.1.1/aimemoryos/memory/importance.py +187 -0
- aimemoryos-0.1.1/aimemoryos/memory/models.py +384 -0
- aimemoryos-0.1.1/aimemoryos/memory/procedural.py +60 -0
- aimemoryos-0.1.1/aimemoryos/memory/semantic.py +67 -0
- aimemoryos-0.1.1/aimemoryos/memory/working.py +44 -0
- aimemoryos-0.1.1/aimemoryos/retrieval/__init__.py +2 -0
- aimemoryos-0.1.1/aimemoryos/retrieval/context_assembler.py +309 -0
- aimemoryos-0.1.1/aimemoryos/retrieval/engine.py +494 -0
- aimemoryos-0.1.1/aimemoryos/retrieval/graph_retriever.py +284 -0
- aimemoryos-0.1.1/aimemoryos/retrieval/spreading_activation.py +41 -0
- aimemoryos-0.1.1/aimemoryos/retrieval/vector_retriever.py +179 -0
- aimemoryos-0.1.1/aimemoryos/storage/__init__.py +2 -0
- aimemoryos-0.1.1/aimemoryos/storage/duckdb_store.py +1125 -0
- aimemoryos-0.1.1/aimemoryos/storage/faiss_store.py +372 -0
- aimemoryos-0.1.1/aimemoryos/storage/hallucination_firewall.py +79 -0
- aimemoryos-0.1.1/aimemoryos/storage/orchestrator.py +511 -0
- aimemoryos-0.1.1/aimemoryos/storage/sqlite_log.py +967 -0
- aimemoryos-0.1.1/aimemoryos/utils/__init__.py +1 -0
- aimemoryos-0.1.1/aimemoryos/utils/hashing.py +147 -0
- aimemoryos-0.1.1/aimemoryos/utils/logger.py +210 -0
- aimemoryos-0.1.1/aimemoryos/utils/metrics.py +453 -0
- aimemoryos-0.1.1/aimemoryos/utils/scoring.py +247 -0
- aimemoryos-0.1.1/aimemoryos/vector/__init__.py +2 -0
- aimemoryos-0.1.1/aimemoryos/vector/cache_embedder.py +271 -0
- aimemoryos-0.1.1/aimemoryos/vector/embedder.py +115 -0
- aimemoryos-0.1.1/aimemoryos/vector/index_manager.py +465 -0
- aimemoryos-0.1.1/aimemoryos.egg-info/PKG-INFO +159 -0
- aimemoryos-0.1.1/aimemoryos.egg-info/SOURCES.txt +64 -0
- aimemoryos-0.1.1/aimemoryos.egg-info/dependency_links.txt +1 -0
- aimemoryos-0.1.1/aimemoryos.egg-info/requires.txt +16 -0
- aimemoryos-0.1.1/aimemoryos.egg-info/top_level.txt +1 -0
- aimemoryos-0.1.1/pyproject.toml +31 -0
- aimemoryos-0.1.1/setup.cfg +4 -0
- aimemoryos-0.1.1/tests/test_detach_delete.py +9 -0
- aimemoryos-0.1.1/tests/test_firewall.py +76 -0
- aimemoryos-0.1.1/tests/test_forget_snapshot.py +33 -0
- aimemoryos-0.1.1/tests/test_index_manager_persistence.py +68 -0
- aimemoryos-0.1.1/tests/test_kuzu.py +20 -0
- aimemoryos-0.1.1/tests/test_kuzu2.py +18 -0
- aimemoryos-0.1.1/tests/test_memory_client.py +110 -0
- aimemoryos-0.1.1/tests/test_utils_modules.py +120 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aimemoryos
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: asyncio>=4.0.0
|
|
8
|
+
Requires-Dist: duckdb>=1.5.3
|
|
9
|
+
Requires-Dist: faiss-cpu>=1.14.2
|
|
10
|
+
Requires-Dist: google>=3.0.0
|
|
11
|
+
Requires-Dist: kuzu>=0.11.3
|
|
12
|
+
Requires-Dist: loguru>=0.7.3
|
|
13
|
+
Requires-Dist: numpy>=2.4.6
|
|
14
|
+
Requires-Dist: openrouter>=0.9.1
|
|
15
|
+
Requires-Dist: orjson>=3.11.9
|
|
16
|
+
Requires-Dist: pydantic>=2.13.4
|
|
17
|
+
Requires-Dist: pydantic-settings>=2.14.1
|
|
18
|
+
Requires-Dist: pymupdf>=1.27.2.3
|
|
19
|
+
Requires-Dist: pytest>=9.0.3
|
|
20
|
+
Requires-Dist: sentence-transformers>=5.5.1
|
|
21
|
+
Requires-Dist: spacy>=3.8.14
|
|
22
|
+
Requires-Dist: structlog>=25.5.0
|
|
23
|
+
|
|
24
|
+
# AIMemoryOS
|
|
25
|
+
|
|
26
|
+
A powerful, modular, multi-store memory layer for conversational agents and LLM applications.
|
|
27
|
+
|
|
28
|
+
AIMemoryOS combines vector retrieval, graph knowledge bases, and highly-durable structured storage to give your AI agents a robust, scalable long-term memory that you can plug and play directly into your own applications.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Multi-Store Architecture**: Coordinated state across SQLite (append-only events), DuckDB (canonical relational state), FAISS (vector similarity), and KuzuDB (graph relationships).
|
|
33
|
+
- **Advanced Ingestion**: End-to-end pipeline with preprocessing, PII stripping, SHA-256 deduplication, and semantic chunking.
|
|
34
|
+
- **Rich Retrieval**: Context assembly from multiple sources, reranking, and trace scoring.
|
|
35
|
+
- **Coordinated Pruning**: Safely `forget()` memories with guaranteed cleanup across all storage backends without violating relational integrity.
|
|
36
|
+
- **Snapshotting**: Take portable, verified SQLite backups of your system's event history instantly.
|
|
37
|
+
- **Hallucination Firewall**: Isolate "hypothesized" or "imagined" memories to separate tables so they don't pollute your agent's ground-truth recall.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
> [!IMPORTANT]
|
|
44
|
+
> **Python Version Requirement**: AIMemoryOS depends on heavy AI libraries (like SpaCy, NumPy, and PyTorch) that require pre-compiled C++ binaries. You **must** use a stable Python version (**Python 3.10, 3.11, or 3.12**) to install this package successfully. Pre-release or cutting-edge versions (like Python 3.13+) will fail to compile.
|
|
45
|
+
|
|
46
|
+
AIMemoryOS relies on powerful machine learning models under the hood. Make sure you have at least 2GB of free disk space before installing, as it will download heavy dependencies like PyTorch, FAISS, and HuggingFace Transformers.
|
|
47
|
+
|
|
48
|
+
### Step-by-step Installation
|
|
49
|
+
|
|
50
|
+
**Step 1: Create a safe virtual environment**
|
|
51
|
+
We highly recommend using a virtual environment strictly locked to a stable Python version. We suggest using [uv](https://docs.astral.sh/uv/) for incredibly fast and foolproof environment isolation:
|
|
52
|
+
```bash
|
|
53
|
+
# Create a Python 3.12 virtual environment
|
|
54
|
+
uv venv --python 3.12
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Step 2: Activate the environment**
|
|
58
|
+
- **Windows (PowerShell)**: `.\.venv\Scripts\activate`
|
|
59
|
+
- **Mac/Linux**: `source .venv/bin/activate`
|
|
60
|
+
|
|
61
|
+
**Step 3: Install AIMemoryOS**
|
|
62
|
+
Install the SDK directly from PyPI into your isolated environment:
|
|
63
|
+
```bash
|
|
64
|
+
uv pip install aimemoryos
|
|
65
|
+
```
|
|
66
|
+
*(If you are using standard `pip`, simply run `python -m pip install aimemoryos` instead).*
|
|
67
|
+
|
|
68
|
+
**Step 4: Download the SpaCy language model weights**
|
|
69
|
+
The `aimemoryos` package will automatically install the `spacy` library for you. However, you must explicitly download the actual **English NLP model weights** (which pip cannot bundle) used for entity extraction:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# If using uv:
|
|
73
|
+
uv run python -m spacy download en_core_web_sm
|
|
74
|
+
|
|
75
|
+
# If using standard python:
|
|
76
|
+
python -m spacy download en_core_web_sm
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Quickstart (Plug & Play)
|
|
82
|
+
|
|
83
|
+
The easiest way to integrate AIMemoryOS into your codebase is by using the `Memory` facade. Simply import it into your application and start saving/retrieving knowledge.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import asyncio
|
|
87
|
+
from pathlib import Path
|
|
88
|
+
from aimemoryos import Memory
|
|
89
|
+
|
|
90
|
+
async def run():
|
|
91
|
+
# Initialize the high-level memory facade
|
|
92
|
+
memory = Memory()
|
|
93
|
+
|
|
94
|
+
# 1. Ingest information into the memory pipeline
|
|
95
|
+
print("Ingesting memory...")
|
|
96
|
+
await memory.save(
|
|
97
|
+
document_id="doc_001",
|
|
98
|
+
content="My favorite programming language is Python and I love pizza."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# 2. Retrieve relevant context based on semantic similarity
|
|
102
|
+
print("Retrieving context...")
|
|
103
|
+
results = await memory.retrieve(
|
|
104
|
+
query="What foods do I like?",
|
|
105
|
+
top_k=5,
|
|
106
|
+
score_threshold=0.2
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Results include the content, metadata, and relevance score
|
|
110
|
+
for r in results:
|
|
111
|
+
print(f"[{r['score']:.2f}] {r['metadata']['content']}")
|
|
112
|
+
|
|
113
|
+
# 3. Forget a memory across all stores (DuckDB, FAISS, Graph, SQLite)
|
|
114
|
+
if results:
|
|
115
|
+
memory_id = results[0]["metadata"]["memory_id"]
|
|
116
|
+
await memory.forget(memory_id=memory_id)
|
|
117
|
+
|
|
118
|
+
# 4. Take a verifiable snapshot of the entire event history
|
|
119
|
+
await memory.snapshot(output_path=Path("backups/memory_snapshot.sqlite"))
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
asyncio.run(run())
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Configuration
|
|
128
|
+
|
|
129
|
+
By default, runtime databases and indexes are stored in the `.aimemoryos/` folder at the root of the installed package. You can easily override this globally if you want your data saved elsewhere:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
export AIMEMORYOS_DATA_DIR="/path/to/custom/data"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Architecture / Project Layout
|
|
138
|
+
|
|
139
|
+
For those interested in extending the SDK or contributing, the folder architecture is maintained as follows:
|
|
140
|
+
|
|
141
|
+
- `agents/`: SDK-facing components. Contains `MemoryClient`, which is the primary unified interface to the ecosystem.
|
|
142
|
+
- `main.py`: A lightweight, backwards-compatible wrapper around `MemoryClient` for immediate plug-and-play.
|
|
143
|
+
- `core/runtime.py`: The composition root. Handles dependency injection, configuration, and instantiating storage/retrieval services.
|
|
144
|
+
- `ingestion/`: Text preprocessing, SHA deduplication, routing, and chunking boundaries.
|
|
145
|
+
- `retrieval/`: Similarity search, vector reranking, and `ContextBuilder` logic.
|
|
146
|
+
- `storage/`: Highly durable persistence backends (`duckdb_store.py`, `faiss_store.py`, `sqlite_log.py`) and the `orchestrator.py`.
|
|
147
|
+
- `vector/`: Embedding generation (`sentence-transformers`) and model management.
|
|
148
|
+
- `graph/`: KuzuDB ontology and schema definitions.
|
|
149
|
+
|
|
150
|
+
## Contributing
|
|
151
|
+
|
|
152
|
+
We welcome contributions! To set up for local development:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
git clone https://github.com/your-org/aimemoryos.git
|
|
156
|
+
cd aimemoryos
|
|
157
|
+
pip install -e .
|
|
158
|
+
pytest -q
|
|
159
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# AIMemoryOS
|
|
2
|
+
|
|
3
|
+
A powerful, modular, multi-store memory layer for conversational agents and LLM applications.
|
|
4
|
+
|
|
5
|
+
AIMemoryOS combines vector retrieval, graph knowledge bases, and highly-durable structured storage to give your AI agents a robust, scalable long-term memory that you can plug and play directly into your own applications.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Multi-Store Architecture**: Coordinated state across SQLite (append-only events), DuckDB (canonical relational state), FAISS (vector similarity), and KuzuDB (graph relationships).
|
|
10
|
+
- **Advanced Ingestion**: End-to-end pipeline with preprocessing, PII stripping, SHA-256 deduplication, and semantic chunking.
|
|
11
|
+
- **Rich Retrieval**: Context assembly from multiple sources, reranking, and trace scoring.
|
|
12
|
+
- **Coordinated Pruning**: Safely `forget()` memories with guaranteed cleanup across all storage backends without violating relational integrity.
|
|
13
|
+
- **Snapshotting**: Take portable, verified SQLite backups of your system's event history instantly.
|
|
14
|
+
- **Hallucination Firewall**: Isolate "hypothesized" or "imagined" memories to separate tables so they don't pollute your agent's ground-truth recall.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
> [!IMPORTANT]
|
|
21
|
+
> **Python Version Requirement**: AIMemoryOS depends on heavy AI libraries (like SpaCy, NumPy, and PyTorch) that require pre-compiled C++ binaries. You **must** use a stable Python version (**Python 3.10, 3.11, or 3.12**) to install this package successfully. Pre-release or cutting-edge versions (like Python 3.13+) will fail to compile.
|
|
22
|
+
|
|
23
|
+
AIMemoryOS relies on powerful machine learning models under the hood. Make sure you have at least 2GB of free disk space before installing, as it will download heavy dependencies like PyTorch, FAISS, and HuggingFace Transformers.
|
|
24
|
+
|
|
25
|
+
### Step-by-step Installation
|
|
26
|
+
|
|
27
|
+
**Step 1: Create a safe virtual environment**
|
|
28
|
+
We highly recommend using a virtual environment strictly locked to a stable Python version. We suggest using [uv](https://docs.astral.sh/uv/) for incredibly fast and foolproof environment isolation:
|
|
29
|
+
```bash
|
|
30
|
+
# Create a Python 3.12 virtual environment
|
|
31
|
+
uv venv --python 3.12
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Step 2: Activate the environment**
|
|
35
|
+
- **Windows (PowerShell)**: `.\.venv\Scripts\activate`
|
|
36
|
+
- **Mac/Linux**: `source .venv/bin/activate`
|
|
37
|
+
|
|
38
|
+
**Step 3: Install AIMemoryOS**
|
|
39
|
+
Install the SDK directly from PyPI into your isolated environment:
|
|
40
|
+
```bash
|
|
41
|
+
uv pip install aimemoryos
|
|
42
|
+
```
|
|
43
|
+
*(If you are using standard `pip`, simply run `python -m pip install aimemoryos` instead).*
|
|
44
|
+
|
|
45
|
+
**Step 4: Download the SpaCy language model weights**
|
|
46
|
+
The `aimemoryos` package will automatically install the `spacy` library for you. However, you must explicitly download the actual **English NLP model weights** (which pip cannot bundle) used for entity extraction:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# If using uv:
|
|
50
|
+
uv run python -m spacy download en_core_web_sm
|
|
51
|
+
|
|
52
|
+
# If using standard python:
|
|
53
|
+
python -m spacy download en_core_web_sm
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Quickstart (Plug & Play)
|
|
59
|
+
|
|
60
|
+
The easiest way to integrate AIMemoryOS into your codebase is by using the `Memory` facade. Simply import it into your application and start saving/retrieving knowledge.
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import asyncio
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
from aimemoryos import Memory
|
|
66
|
+
|
|
67
|
+
async def run():
|
|
68
|
+
# Initialize the high-level memory facade
|
|
69
|
+
memory = Memory()
|
|
70
|
+
|
|
71
|
+
# 1. Ingest information into the memory pipeline
|
|
72
|
+
print("Ingesting memory...")
|
|
73
|
+
await memory.save(
|
|
74
|
+
document_id="doc_001",
|
|
75
|
+
content="My favorite programming language is Python and I love pizza."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# 2. Retrieve relevant context based on semantic similarity
|
|
79
|
+
print("Retrieving context...")
|
|
80
|
+
results = await memory.retrieve(
|
|
81
|
+
query="What foods do I like?",
|
|
82
|
+
top_k=5,
|
|
83
|
+
score_threshold=0.2
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Results include the content, metadata, and relevance score
|
|
87
|
+
for r in results:
|
|
88
|
+
print(f"[{r['score']:.2f}] {r['metadata']['content']}")
|
|
89
|
+
|
|
90
|
+
# 3. Forget a memory across all stores (DuckDB, FAISS, Graph, SQLite)
|
|
91
|
+
if results:
|
|
92
|
+
memory_id = results[0]["metadata"]["memory_id"]
|
|
93
|
+
await memory.forget(memory_id=memory_id)
|
|
94
|
+
|
|
95
|
+
# 4. Take a verifiable snapshot of the entire event history
|
|
96
|
+
await memory.snapshot(output_path=Path("backups/memory_snapshot.sqlite"))
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
asyncio.run(run())
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
By default, runtime databases and indexes are stored in the `.aimemoryos/` folder at the root of the installed package. You can easily override this globally if you want your data saved elsewhere:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export AIMEMORYOS_DATA_DIR="/path/to/custom/data"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Architecture / Project Layout
|
|
115
|
+
|
|
116
|
+
For those interested in extending the SDK or contributing, the folder architecture is maintained as follows:
|
|
117
|
+
|
|
118
|
+
- `agents/`: SDK-facing components. Contains `MemoryClient`, which is the primary unified interface to the ecosystem.
|
|
119
|
+
- `main.py`: A lightweight, backwards-compatible wrapper around `MemoryClient` for immediate plug-and-play.
|
|
120
|
+
- `core/runtime.py`: The composition root. Handles dependency injection, configuration, and instantiating storage/retrieval services.
|
|
121
|
+
- `ingestion/`: Text preprocessing, SHA deduplication, routing, and chunking boundaries.
|
|
122
|
+
- `retrieval/`: Similarity search, vector reranking, and `ContextBuilder` logic.
|
|
123
|
+
- `storage/`: Highly durable persistence backends (`duckdb_store.py`, `faiss_store.py`, `sqlite_log.py`) and the `orchestrator.py`.
|
|
124
|
+
- `vector/`: Embedding generation (`sentence-transformers`) and model management.
|
|
125
|
+
- `graph/`: KuzuDB ontology and schema definitions.
|
|
126
|
+
|
|
127
|
+
## Contributing
|
|
128
|
+
|
|
129
|
+
We welcome contributions! To set up for local development:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
git clone https://github.com/your-org/aimemoryos.git
|
|
133
|
+
cd aimemoryos
|
|
134
|
+
pip install -e .
|
|
135
|
+
pytest -q
|
|
136
|
+
```
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Iterable
|
|
5
|
+
|
|
6
|
+
from aimemoryos.retrieval.context_assembler import ContextBlock
|
|
7
|
+
|
|
8
|
+
# ---------------------------------------------------------------------------
|
|
9
|
+
# Defaults
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
DEFAULT_HEADER: str = "Relevant Memory Context"
|
|
13
|
+
DEFAULT_SEPARATOR: str = "\n\n"
|
|
14
|
+
DEFAULT_MAX_MEMORIES: int = 10
|
|
15
|
+
|
|
16
|
+
_CHARS_PER_TOKEN: int = 4
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class BuiltContext:
|
|
20
|
+
"""
|
|
21
|
+
Final prompt-ready context payload returned by ContextBuilder.
|
|
22
|
+
|
|
23
|
+
Fields
|
|
24
|
+
------
|
|
25
|
+
text : the assembled string ready for LLM injection
|
|
26
|
+
memory_count : number of memories included (≤ max_memories)
|
|
27
|
+
truncated : True when source list exceeded max_memories
|
|
28
|
+
estimated_tokens : rough token count (len(text) // 4, min 1)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
text: str
|
|
32
|
+
memory_count: int
|
|
33
|
+
truncated: bool
|
|
34
|
+
estimated_tokens: int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContextBuilder:
|
|
38
|
+
"""
|
|
39
|
+
Prompt-oriented context assembler.
|
|
40
|
+
|
|
41
|
+
Responsibilities
|
|
42
|
+
----------------
|
|
43
|
+
- ContextBlock → prompt string formatting
|
|
44
|
+
- Memory ordering preservation (caller supplies ranked order)
|
|
45
|
+
- Optional score / metadata annotation per memory block
|
|
46
|
+
- Token budget estimation via char-count heuristic
|
|
47
|
+
- Truncation to max_memories
|
|
48
|
+
|
|
49
|
+
Does NOT:
|
|
50
|
+
- retrieve memories
|
|
51
|
+
- rerank memories
|
|
52
|
+
- access any storage backend
|
|
53
|
+
- mutate memories
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
*,
|
|
59
|
+
max_memories: int = DEFAULT_MAX_MEMORIES,
|
|
60
|
+
separator: str = DEFAULT_SEPARATOR,
|
|
61
|
+
include_metadata: bool = False,
|
|
62
|
+
include_scores: bool = False,
|
|
63
|
+
) -> None:
|
|
64
|
+
self.max_memories = max_memories
|
|
65
|
+
self.separator = separator
|
|
66
|
+
self.include_metadata = include_metadata
|
|
67
|
+
self.include_scores = include_scores
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------------------
|
|
70
|
+
# Primary build path
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def build(self, context: ContextBlock) -> BuiltContext:
|
|
74
|
+
"""
|
|
75
|
+
Convert a ContextBlock into a prompt-ready BuiltContext.
|
|
76
|
+
|
|
77
|
+
Memory ordering is preserved from context.memories — the retrieval
|
|
78
|
+
engine has already applied RRF fusion and importance weighting.
|
|
79
|
+
Truncation to max_memories is applied here if the list is longer.
|
|
80
|
+
|
|
81
|
+
Each memory is rendered by _render_memory(), which appends optional
|
|
82
|
+
score and metadata lines controlled by include_scores /
|
|
83
|
+
include_metadata. The full output is prefixed with DEFAULT_HEADER.
|
|
84
|
+
"""
|
|
85
|
+
selected = context.memories[: self.max_memories]
|
|
86
|
+
truncated = len(context.memories) > self.max_memories
|
|
87
|
+
|
|
88
|
+
sections: list[str] = [DEFAULT_HEADER]
|
|
89
|
+
for index, memory in enumerate(selected, start=1):
|
|
90
|
+
sections.append(self._render_memory(index=index, memory=memory))
|
|
91
|
+
|
|
92
|
+
text = self.separator.join(sections)
|
|
93
|
+
return BuiltContext(
|
|
94
|
+
text=text,
|
|
95
|
+
memory_count=len(selected),
|
|
96
|
+
truncated=truncated,
|
|
97
|
+
estimated_tokens=self._estimate_tokens(text),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------------
|
|
101
|
+
# Lightweight raw-string path
|
|
102
|
+
# ------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
def build_raw(self, memories: Iterable[str]) -> BuiltContext:
|
|
105
|
+
"""
|
|
106
|
+
Assemble a BuiltContext from an iterable of pre-formatted strings.
|
|
107
|
+
|
|
108
|
+
Truncates to max_memories. No header is prepended. Useful when the
|
|
109
|
+
caller has already formatted memory strings and only needs truncation
|
|
110
|
+
and token estimation (e.g. unit tests, custom formatting pipelines).
|
|
111
|
+
"""
|
|
112
|
+
all_items = list(memories)
|
|
113
|
+
truncated = len(all_items) > self.max_memories
|
|
114
|
+
selected = all_items[: self.max_memories]
|
|
115
|
+
text = self.separator.join(selected)
|
|
116
|
+
return BuiltContext(
|
|
117
|
+
text=text,
|
|
118
|
+
memory_count=len(selected),
|
|
119
|
+
truncated=truncated,
|
|
120
|
+
estimated_tokens=self._estimate_tokens(text),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# ------------------------------------------------------------------
|
|
124
|
+
# Memory rendering
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
def _render_memory(self, *, index: int, memory: object) -> str:
|
|
128
|
+
"""
|
|
129
|
+
Format a single RetrievalCandidate into a prompt block.
|
|
130
|
+
|
|
131
|
+
Always includes:
|
|
132
|
+
[Memory N]
|
|
133
|
+
<content>
|
|
134
|
+
|
|
135
|
+
Conditionally includes (controlled by constructor flags):
|
|
136
|
+
[score=X.XXXX] — when include_scores=True and trace present
|
|
137
|
+
[metadata={...}] — when include_metadata=True and trace metadata present
|
|
138
|
+
"""
|
|
139
|
+
lines: list[str] = []
|
|
140
|
+
lines.append(f"[Memory {index}]")
|
|
141
|
+
lines.append(getattr(memory, "content", ""))
|
|
142
|
+
|
|
143
|
+
if self.include_scores:
|
|
144
|
+
trace = getattr(memory, "trace", None)
|
|
145
|
+
if trace is not None:
|
|
146
|
+
score = getattr(trace, "final_score", None)
|
|
147
|
+
if score is not None:
|
|
148
|
+
lines.append(f"[score={score:.4f}]")
|
|
149
|
+
|
|
150
|
+
if self.include_metadata:
|
|
151
|
+
trace = getattr(memory, "trace", None)
|
|
152
|
+
metadata = getattr(trace, "trace_metadata", None) if trace else None
|
|
153
|
+
if metadata:
|
|
154
|
+
lines.append(f"[metadata={metadata}]")
|
|
155
|
+
|
|
156
|
+
return "\n".join(lines)
|
|
157
|
+
|
|
158
|
+
# ------------------------------------------------------------------
|
|
159
|
+
# Token estimation
|
|
160
|
+
# ------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _estimate_tokens(text: str) -> int:
|
|
164
|
+
"""
|
|
165
|
+
Rough token count: 1 token ≈ 4 characters (min 1).
|
|
166
|
+
|
|
167
|
+
This is a heuristic. For budget-critical prompts, run a proper
|
|
168
|
+
tokeniser (e.g. tiktoken) on BuiltContext.text before submission.
|
|
169
|
+
"""
|
|
170
|
+
return max(len(text) // _CHARS_PER_TOKEN, 1)
|
|
171
|
+
|