mda-memory 0.1.2__tar.gz → 0.2.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.
- mda_memory-0.2.0/PKG-INFO +225 -0
- mda_memory-0.2.0/README.md +200 -0
- mda_memory-0.2.0/mda/__init__.py +4 -0
- mda_memory-0.2.0/mda/core/accelerator.py +154 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/core/bind.py +59 -39
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/core/encoder.py +723 -688
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/core/entity.py +373 -319
- mda_memory-0.2.0/mda/core/event.py +345 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/core/registry.py +218 -157
- mda_memory-0.2.0/mda/inference/associative.py +331 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/inference/broca.py +353 -204
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/inference/memory.py +23 -0
- mda_memory-0.2.0/mda/inference/reasoning.py +301 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/inference/translator.py +220 -218
- mda_memory-0.2.0/mda/integrations/__init__.py +3 -0
- mda_memory-0.2.0/mda/integrations/agent.py +150 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/integrations/engine.py +1285 -837
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/integrations/loader.py +130 -8
- mda_memory-0.2.0/mda/integrations/mcp_server.py +381 -0
- mda_memory-0.2.0/mda/integrations/owui_function.py +112 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/mda.py +886 -561
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/training/checkpoint.py +206 -192
- mda_memory-0.2.0/mda_memory.egg-info/PKG-INFO +225 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda_memory.egg-info/SOURCES.txt +8 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda_memory.egg-info/entry_points.txt +1 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda_memory.egg-info/requires.txt +1 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/pyproject.toml +4 -2
- mda_memory-0.2.0/tests/test_agent.py +183 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_engine.py +27 -2
- mda_memory-0.2.0/tests/test_event.py +366 -0
- mda_memory-0.2.0/tests/test_gpu_port.py +151 -0
- mda_memory-0.1.2/PKG-INFO +0 -227
- mda_memory-0.1.2/README.md +0 -203
- mda_memory-0.1.2/mda/__init__.py +0 -3
- mda_memory-0.1.2/mda/inference/associative.py +0 -190
- mda_memory-0.1.2/mda/inference/reasoning.py +0 -169
- mda_memory-0.1.2/mda/training/__init__.py +0 -0
- mda_memory-0.1.2/mda_memory.egg-info/PKG-INFO +0 -227
- {mda_memory-0.1.2 → mda_memory-0.2.0}/LICENSE +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/core/__init__.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/core/neuron.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/data/__init__.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/data/stopwords.txt +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/data/stopwords_content.txt +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/inference/__init__.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda/integrations/cli.py +0 -0
- {mda_memory-0.1.2/mda/integrations → mda_memory-0.2.0/mda/training}/__init__.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda_memory.egg-info/dependency_links.txt +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/mda_memory.egg-info/top_level.txt +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/setup.cfg +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_associative.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_bind.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_broca.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_encoder.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_entity.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_mda.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_memory.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_neuron.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_reasoning.py +0 -0
- {mda_memory-0.1.2 → mda_memory-0.2.0}/tests/test_registry.py +0 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mda-memory
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Online associative memory for LLMs
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: open-webui
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: torch
|
|
11
|
+
Requires-Dist: rich
|
|
12
|
+
Requires-Dist: prompt_toolkit
|
|
13
|
+
Requires-Dist: python-dotenv
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Requires-Dist: anthropic
|
|
16
|
+
Requires-Dist: ollama
|
|
17
|
+
Requires-Dist: chromadb
|
|
18
|
+
Requires-Dist: sentence-transformers
|
|
19
|
+
Requires-Dist: tqdm
|
|
20
|
+
Requires-Dist: matplotlib
|
|
21
|
+
Requires-Dist: ijson
|
|
22
|
+
Requires-Dist: deep-translator
|
|
23
|
+
Requires-Dist: langdetect
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Modular Dynamic Architecture (MDA)
|
|
27
|
+
|
|
28
|
+
**Online associative memory for LLMs. Learns during inference. No backpropagation.**
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/mda-memory/)
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](https://www.python.org/)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## What is MDA?
|
|
37
|
+
|
|
38
|
+
Large language models can reason but cannot remember. RAG partially addresses this but cannot update during a conversation or learn from it.
|
|
39
|
+
|
|
40
|
+
MDA fills precisely these gaps.
|
|
41
|
+
|
|
42
|
+
It encodes knowledge as **512-dimensional Holographic Distributed Representations (HDRs)**, connects concepts through a **sparse synapse graph**, and retrieves context by activating entity networks not by text-chunk similarity search. New knowledge is integrated immediately, without rebuilding any index.
|
|
43
|
+
|
|
44
|
+
**MDA is not a RAG replacement. It is the persistent learning layer that RAG and LLMs are missing.**
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Key Properties
|
|
51
|
+
|
|
52
|
+
- **Token-free** — no tokenizer, no vocabulary
|
|
53
|
+
- **Attention-free** — no transformer encoder required
|
|
54
|
+
- **Online learning** — learns during inference via the Oja rule
|
|
55
|
+
- **No catastrophic forgetting** — entities are independent; new knowledge never overwrites old
|
|
56
|
+
- **CPU-first** — runs on numpy; GPU acceleration via PyTorch when available
|
|
57
|
+
- **Model-agnostic** — works with Ollama, OpenAI, Anthropic, llama.cpp, or any LLM
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install mda-memory
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from mda.integrations.engine import MDAEngine
|
|
69
|
+
|
|
70
|
+
engine = MDAEngine(model="qwen3:4b", user_id="demo")
|
|
71
|
+
engine.learn("Solaris Station was founded by Dr. Mira Voss in 2041.")
|
|
72
|
+
|
|
73
|
+
response = engine.chat("Who founded Solaris?")
|
|
74
|
+
print(response)
|
|
75
|
+
# Dr. Mira Voss founded Solaris Station in 2041.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### CLI
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
mda --model qwen3:4b
|
|
82
|
+
mda --model claude-haiku-4-5-20251001 --provider anthropic
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## How Memory Works
|
|
88
|
+
|
|
89
|
+
Each turn, MDA retrieves relevant context from previous turns and injects it into the LLM prompt with confidence scores, inferred connections, and structured events. Memory grows and strengthens across the conversation without any reindexing.
|
|
90
|
+
|
|
91
|
+

|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Benchmark Results
|
|
96
|
+
|
|
97
|
+

|
|
98
|
+
|
|
99
|
+
Evaluated against a strong RAG baseline (bge-large-en-v1.5 + ChromaDB, top-6 retrieval) across 80 questions spanning 8 cognitive categories:
|
|
100
|
+
|
|
101
|
+
| Category | RAG | MDA | Δ |
|
|
102
|
+
|---|---|---|---|
|
|
103
|
+
| ATOMIC_RECALL | 100% | 85% | −15% |
|
|
104
|
+
| MULTI_HOP | 90% | 90% | 0% |
|
|
105
|
+
| CROSS_DOCUMENT | 80% | 70% | −10% |
|
|
106
|
+
| REASONING | 70% | 90% | **+20%** |
|
|
107
|
+
| INCREMENTAL_LEARNING | 0% | 60% | **+60%** |
|
|
108
|
+
| NOISE_RESISTANCE | 100% | 100% | 0% |
|
|
109
|
+
| MEMORY_COMPRESSION | 90% | 70% | −20% |
|
|
110
|
+
| BOUNDARY | 100% | 100% | 0% |
|
|
111
|
+
| **OVERALL** | **78.8%** | **83.1%** | **+4.3%** |
|
|
112
|
+
|
|
113
|
+
MDA uses **3.1× less context per query** than RAG while achieving higher overall accuracy.
|
|
114
|
+
|
|
115
|
+
**Long-context retention (200 turns):** RAG 0% — MDA 92%.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
### Entity & W Matrix
|
|
122
|
+
Every concept is an `Entity` with a 512-dim identity vector `v` and a lazy-initialized weight matrix `W` (512×512). `W` is `None` until first activation — memory overhead is proportional to usage.
|
|
123
|
+
|
|
124
|
+
### Online Learning (Oja Rule)
|
|
125
|
+
```
|
|
126
|
+
ΔW = η(yxᵀ − y²W)
|
|
127
|
+
```
|
|
128
|
+
No backpropagation. No gradient descent. Runs in O(d²) per entity per turn.
|
|
129
|
+
|
|
130
|
+
### AssociativeChain
|
|
131
|
+
Query → origin entity → BFS synapse traversal (depth 3-6) → context assembly. Dynamic inhibition threshold cached per entity count.
|
|
132
|
+
|
|
133
|
+
### BrocaModule
|
|
134
|
+
```
|
|
135
|
+
score = 0.35·s_query + 0.45·s_W + 0.20·s_sense
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Open WebUI Integration
|
|
141
|
+
|
|
142
|
+
MDA works as a native Open WebUI Filter Function — zero pipeline server required.
|
|
143
|
+
|
|
144
|
+
1. Copy `mda/integrations/owui_function.py` contents
|
|
145
|
+
2. Open WebUI → Admin Panel → Functions → "+" → paste → Save
|
|
146
|
+
3. Enable globally
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## AgentMemory
|
|
151
|
+
|
|
152
|
+
Tool-call aware memory layer for agentic loops. No LLM call inside — pure memory read/write with typed, step-indexed observations.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from mda import AgentMemory
|
|
156
|
+
|
|
157
|
+
agent_mem = AgentMemory()
|
|
158
|
+
|
|
159
|
+
# After each tool call
|
|
160
|
+
agent_mem.observe("http_get", "status 200 OK", step=1)
|
|
161
|
+
agent_mem.error("payments API /v2/charge returned 410 Gone", step=7)
|
|
162
|
+
agent_mem.decision("using batch mode because rate limit is 100/min", step=12)
|
|
163
|
+
agent_mem.environment("API base URL is https://api.example.com/v3")
|
|
164
|
+
|
|
165
|
+
# Before next decision
|
|
166
|
+
context = agent_mem.query("payments API")
|
|
167
|
+
# → [ERROR@7] payments API /v2/charge returned 410 Gone
|
|
168
|
+
# → [DECISION@12] using batch mode because rate limit is 100/min
|
|
169
|
+
|
|
170
|
+
# Detect repeated attempts
|
|
171
|
+
if agent_mem.is_looping("retry charge endpoint"):
|
|
172
|
+
agent_mem.decision("switching to /v3/charge endpoint", step=15)
|
|
173
|
+
|
|
174
|
+
# Find related past errors
|
|
175
|
+
related = agent_mem.similar_errors("connection refused")
|
|
176
|
+
|
|
177
|
+
# Persist & restore across runs
|
|
178
|
+
agent_mem.save()
|
|
179
|
+
agent_mem.load()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Context tags: `[OBSERVE@N tool]`, `[ERROR@N]`, `[DECISION@N]`, `[ENV]`
|
|
183
|
+
|
|
184
|
+
Checkpoint is written to `.memory/{user_id}/agent_{run_id}.json` relative to the **current working directory** — not the library install path.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Batch Engine
|
|
189
|
+
|
|
190
|
+
For multi-agent workloads or large context windows:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from mda.integrations.engine import MDABatchEngine
|
|
194
|
+
|
|
195
|
+
engine = MDABatchEngine(depth=6, top_k_branches=5)
|
|
196
|
+
contexts = engine.build_context_batch([
|
|
197
|
+
"legal contract risk analysis",
|
|
198
|
+
"MDA memory architecture",
|
|
199
|
+
])
|
|
200
|
+
# depth=6 → 15,625 associative paths per query
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
GPU acceleration activates automatically when PyTorch + CUDA is available.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Roadmap
|
|
208
|
+
|
|
209
|
+
- [x] GPU acceleration — EntityMatrix matmul, persistent tensor cache
|
|
210
|
+
- [x] 512-dim HDR — higher representation capacity
|
|
211
|
+
- [x] Open WebUI integration — native Filter Function
|
|
212
|
+
- [x] Batch engine — N queries in single GPU pass
|
|
213
|
+
- [x] AgentMemory — tool-call aware step-indexed memory for agentic loops
|
|
214
|
+
- [ ] `mda.cloud` API — persistent memory as a service
|
|
215
|
+
- [ ] MDA + RAG hybrid — offline corpus retrieval + online learning
|
|
216
|
+
- [ ] Low-rank W — W ≈ A×B for higher-dimensional HDRs
|
|
217
|
+
- [ ] Independent benchmark — community-constructed evaluation set
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
[SSPL 1.0](LICENSE) — free for research and personal use. Commercial use requires a separate agreement.
|
|
224
|
+
|
|
225
|
+
For commercial licensing: mert@kairfy.com
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Modular Dynamic Architecture (MDA)
|
|
2
|
+
|
|
3
|
+
**Online associative memory for LLMs. Learns during inference. No backpropagation.**
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/mda-memory/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.python.org/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What is MDA?
|
|
12
|
+
|
|
13
|
+
Large language models can reason but cannot remember. RAG partially addresses this but cannot update during a conversation or learn from it.
|
|
14
|
+
|
|
15
|
+
MDA fills precisely these gaps.
|
|
16
|
+
|
|
17
|
+
It encodes knowledge as **512-dimensional Holographic Distributed Representations (HDRs)**, connects concepts through a **sparse synapse graph**, and retrieves context by activating entity networks not by text-chunk similarity search. New knowledge is integrated immediately, without rebuilding any index.
|
|
18
|
+
|
|
19
|
+
**MDA is not a RAG replacement. It is the persistent learning layer that RAG and LLMs are missing.**
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Key Properties
|
|
26
|
+
|
|
27
|
+
- **Token-free** — no tokenizer, no vocabulary
|
|
28
|
+
- **Attention-free** — no transformer encoder required
|
|
29
|
+
- **Online learning** — learns during inference via the Oja rule
|
|
30
|
+
- **No catastrophic forgetting** — entities are independent; new knowledge never overwrites old
|
|
31
|
+
- **CPU-first** — runs on numpy; GPU acceleration via PyTorch when available
|
|
32
|
+
- **Model-agnostic** — works with Ollama, OpenAI, Anthropic, llama.cpp, or any LLM
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install mda-memory
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from mda.integrations.engine import MDAEngine
|
|
44
|
+
|
|
45
|
+
engine = MDAEngine(model="qwen3:4b", user_id="demo")
|
|
46
|
+
engine.learn("Solaris Station was founded by Dr. Mira Voss in 2041.")
|
|
47
|
+
|
|
48
|
+
response = engine.chat("Who founded Solaris?")
|
|
49
|
+
print(response)
|
|
50
|
+
# Dr. Mira Voss founded Solaris Station in 2041.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### CLI
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
mda --model qwen3:4b
|
|
57
|
+
mda --model claude-haiku-4-5-20251001 --provider anthropic
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## How Memory Works
|
|
63
|
+
|
|
64
|
+
Each turn, MDA retrieves relevant context from previous turns and injects it into the LLM prompt with confidence scores, inferred connections, and structured events. Memory grows and strengthens across the conversation without any reindexing.
|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Benchmark Results
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
Evaluated against a strong RAG baseline (bge-large-en-v1.5 + ChromaDB, top-6 retrieval) across 80 questions spanning 8 cognitive categories:
|
|
75
|
+
|
|
76
|
+
| Category | RAG | MDA | Δ |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| ATOMIC_RECALL | 100% | 85% | −15% |
|
|
79
|
+
| MULTI_HOP | 90% | 90% | 0% |
|
|
80
|
+
| CROSS_DOCUMENT | 80% | 70% | −10% |
|
|
81
|
+
| REASONING | 70% | 90% | **+20%** |
|
|
82
|
+
| INCREMENTAL_LEARNING | 0% | 60% | **+60%** |
|
|
83
|
+
| NOISE_RESISTANCE | 100% | 100% | 0% |
|
|
84
|
+
| MEMORY_COMPRESSION | 90% | 70% | −20% |
|
|
85
|
+
| BOUNDARY | 100% | 100% | 0% |
|
|
86
|
+
| **OVERALL** | **78.8%** | **83.1%** | **+4.3%** |
|
|
87
|
+
|
|
88
|
+
MDA uses **3.1× less context per query** than RAG while achieving higher overall accuracy.
|
|
89
|
+
|
|
90
|
+
**Long-context retention (200 turns):** RAG 0% — MDA 92%.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## How It Works
|
|
95
|
+
|
|
96
|
+
### Entity & W Matrix
|
|
97
|
+
Every concept is an `Entity` with a 512-dim identity vector `v` and a lazy-initialized weight matrix `W` (512×512). `W` is `None` until first activation — memory overhead is proportional to usage.
|
|
98
|
+
|
|
99
|
+
### Online Learning (Oja Rule)
|
|
100
|
+
```
|
|
101
|
+
ΔW = η(yxᵀ − y²W)
|
|
102
|
+
```
|
|
103
|
+
No backpropagation. No gradient descent. Runs in O(d²) per entity per turn.
|
|
104
|
+
|
|
105
|
+
### AssociativeChain
|
|
106
|
+
Query → origin entity → BFS synapse traversal (depth 3-6) → context assembly. Dynamic inhibition threshold cached per entity count.
|
|
107
|
+
|
|
108
|
+
### BrocaModule
|
|
109
|
+
```
|
|
110
|
+
score = 0.35·s_query + 0.45·s_W + 0.20·s_sense
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Open WebUI Integration
|
|
116
|
+
|
|
117
|
+
MDA works as a native Open WebUI Filter Function — zero pipeline server required.
|
|
118
|
+
|
|
119
|
+
1. Copy `mda/integrations/owui_function.py` contents
|
|
120
|
+
2. Open WebUI → Admin Panel → Functions → "+" → paste → Save
|
|
121
|
+
3. Enable globally
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## AgentMemory
|
|
126
|
+
|
|
127
|
+
Tool-call aware memory layer for agentic loops. No LLM call inside — pure memory read/write with typed, step-indexed observations.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from mda import AgentMemory
|
|
131
|
+
|
|
132
|
+
agent_mem = AgentMemory()
|
|
133
|
+
|
|
134
|
+
# After each tool call
|
|
135
|
+
agent_mem.observe("http_get", "status 200 OK", step=1)
|
|
136
|
+
agent_mem.error("payments API /v2/charge returned 410 Gone", step=7)
|
|
137
|
+
agent_mem.decision("using batch mode because rate limit is 100/min", step=12)
|
|
138
|
+
agent_mem.environment("API base URL is https://api.example.com/v3")
|
|
139
|
+
|
|
140
|
+
# Before next decision
|
|
141
|
+
context = agent_mem.query("payments API")
|
|
142
|
+
# → [ERROR@7] payments API /v2/charge returned 410 Gone
|
|
143
|
+
# → [DECISION@12] using batch mode because rate limit is 100/min
|
|
144
|
+
|
|
145
|
+
# Detect repeated attempts
|
|
146
|
+
if agent_mem.is_looping("retry charge endpoint"):
|
|
147
|
+
agent_mem.decision("switching to /v3/charge endpoint", step=15)
|
|
148
|
+
|
|
149
|
+
# Find related past errors
|
|
150
|
+
related = agent_mem.similar_errors("connection refused")
|
|
151
|
+
|
|
152
|
+
# Persist & restore across runs
|
|
153
|
+
agent_mem.save()
|
|
154
|
+
agent_mem.load()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Context tags: `[OBSERVE@N tool]`, `[ERROR@N]`, `[DECISION@N]`, `[ENV]`
|
|
158
|
+
|
|
159
|
+
Checkpoint is written to `.memory/{user_id}/agent_{run_id}.json` relative to the **current working directory** — not the library install path.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Batch Engine
|
|
164
|
+
|
|
165
|
+
For multi-agent workloads or large context windows:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from mda.integrations.engine import MDABatchEngine
|
|
169
|
+
|
|
170
|
+
engine = MDABatchEngine(depth=6, top_k_branches=5)
|
|
171
|
+
contexts = engine.build_context_batch([
|
|
172
|
+
"legal contract risk analysis",
|
|
173
|
+
"MDA memory architecture",
|
|
174
|
+
])
|
|
175
|
+
# depth=6 → 15,625 associative paths per query
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
GPU acceleration activates automatically when PyTorch + CUDA is available.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Roadmap
|
|
183
|
+
|
|
184
|
+
- [x] GPU acceleration — EntityMatrix matmul, persistent tensor cache
|
|
185
|
+
- [x] 512-dim HDR — higher representation capacity
|
|
186
|
+
- [x] Open WebUI integration — native Filter Function
|
|
187
|
+
- [x] Batch engine — N queries in single GPU pass
|
|
188
|
+
- [x] AgentMemory — tool-call aware step-indexed memory for agentic loops
|
|
189
|
+
- [ ] `mda.cloud` API — persistent memory as a service
|
|
190
|
+
- [ ] MDA + RAG hybrid — offline corpus retrieval + online learning
|
|
191
|
+
- [ ] Low-rank W — W ≈ A×B for higher-dimensional HDRs
|
|
192
|
+
- [ ] Independent benchmark — community-constructed evaluation set
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
[SSPL 1.0](LICENSE) — free for research and personal use. Commercial use requires a separate agreement.
|
|
199
|
+
|
|
200
|
+
For commercial licensing: mert@kairfy.com
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mda/core/accelerator.py — numpy/torch adapter.
|
|
3
|
+
|
|
4
|
+
All GPU work happens here; everything else in MDA stays numpy-typed at call
|
|
5
|
+
boundaries. Graceful-fallback pattern: torch is an optional dependency.
|
|
6
|
+
HAS_TORCH / HAS_CUDA / HAS_MPS flags are set at import time and can be
|
|
7
|
+
inspected by callers before dispatching to GPU helpers.
|
|
8
|
+
|
|
9
|
+
Functions that unconditionally require torch (to_t, to_np, normalize_t,
|
|
10
|
+
cosine_t, fft_bind_t) raise RuntimeError when torch is absent rather than
|
|
11
|
+
producing a confusing NameError. Batch helpers (batch_cosine, batch_matmul)
|
|
12
|
+
fall back to numpy silently.
|
|
13
|
+
"""
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import torch
|
|
18
|
+
|
|
19
|
+
HAS_TORCH = True
|
|
20
|
+
HAS_CUDA = torch.cuda.is_available()
|
|
21
|
+
HAS_MPS = (
|
|
22
|
+
getattr(torch.backends, "mps", None) is not None
|
|
23
|
+
and torch.backends.mps.is_available()
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if HAS_CUDA:
|
|
27
|
+
DEVICE = torch.device("cuda")
|
|
28
|
+
elif HAS_MPS:
|
|
29
|
+
DEVICE = torch.device("mps")
|
|
30
|
+
else:
|
|
31
|
+
DEVICE = torch.device("cpu")
|
|
32
|
+
|
|
33
|
+
except ImportError:
|
|
34
|
+
HAS_TORCH = False
|
|
35
|
+
HAS_CUDA = False
|
|
36
|
+
HAS_MPS = False
|
|
37
|
+
DEVICE = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_device():
|
|
41
|
+
"""Return the active torch.device, or None if torch is not installed."""
|
|
42
|
+
return DEVICE
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_t(v: np.ndarray):
|
|
46
|
+
"""np float32 array → contiguous torch.Tensor on DEVICE.
|
|
47
|
+
|
|
48
|
+
Raises RuntimeError if torch is not installed.
|
|
49
|
+
"""
|
|
50
|
+
if not HAS_TORCH:
|
|
51
|
+
raise RuntimeError("to_t requires torch, which is not installed.")
|
|
52
|
+
return torch.from_numpy(np.ascontiguousarray(v)).float().to(DEVICE)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def to_np(t) -> np.ndarray:
|
|
56
|
+
"""torch.Tensor (any device) → np.float32 ndarray.
|
|
57
|
+
|
|
58
|
+
Raises RuntimeError if torch is not installed.
|
|
59
|
+
"""
|
|
60
|
+
if not HAS_TORCH:
|
|
61
|
+
raise RuntimeError("to_np requires torch, which is not installed.")
|
|
62
|
+
return t.detach().cpu().numpy().astype(np.float32)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def normalize_t(v: np.ndarray) -> np.ndarray:
|
|
66
|
+
"""Normalize *v* via torch.linalg.vector_norm on DEVICE; return np.float32.
|
|
67
|
+
|
|
68
|
+
Raises RuntimeError if torch is not installed.
|
|
69
|
+
"""
|
|
70
|
+
if not HAS_TORCH:
|
|
71
|
+
raise RuntimeError("normalize_t requires torch, which is not installed.")
|
|
72
|
+
t = to_t(v)
|
|
73
|
+
n = torch.linalg.vector_norm(t)
|
|
74
|
+
return to_np(t / (n + 1e-8))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cosine_t(a: np.ndarray, b: np.ndarray) -> float:
|
|
78
|
+
"""Cosine similarity via torch.dot on DEVICE; return Python float.
|
|
79
|
+
|
|
80
|
+
Raises RuntimeError if torch is not installed.
|
|
81
|
+
"""
|
|
82
|
+
if not HAS_TORCH:
|
|
83
|
+
raise RuntimeError("cosine_t requires torch, which is not installed.")
|
|
84
|
+
ta = to_t(a)
|
|
85
|
+
tb = to_t(b)
|
|
86
|
+
na = torch.linalg.vector_norm(ta)
|
|
87
|
+
nb = torch.linalg.vector_norm(tb)
|
|
88
|
+
return float((torch.dot(ta, tb) / (na * nb + 1e-8)).item())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def fft_bind_t(a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
92
|
+
"""Circular convolution via torch.fft on DEVICE; return real np.float32.
|
|
93
|
+
|
|
94
|
+
Raises RuntimeError if torch is not installed.
|
|
95
|
+
"""
|
|
96
|
+
if not HAS_TORCH:
|
|
97
|
+
raise RuntimeError("fft_bind_t requires torch, which is not installed.")
|
|
98
|
+
ta = to_t(a)
|
|
99
|
+
tb = to_t(b)
|
|
100
|
+
result = torch.fft.ifft(torch.fft.fft(ta) * torch.fft.fft(tb))
|
|
101
|
+
return to_np(result.real)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def batch_cosine(matrix: np.ndarray, vec: np.ndarray) -> np.ndarray:
|
|
105
|
+
"""(N, D) matrix @ (D,) vec → (N,) cosine scores as np.float32.
|
|
106
|
+
|
|
107
|
+
Matrix rows are assumed to be pre-normalised (unit vectors).
|
|
108
|
+
Uses torch.mv on DEVICE when torch is available; falls back to numpy.
|
|
109
|
+
"""
|
|
110
|
+
if HAS_TORCH:
|
|
111
|
+
tm = to_t(matrix)
|
|
112
|
+
tv = to_t(vec)
|
|
113
|
+
return torch.mv(tm, tv).cpu().numpy()
|
|
114
|
+
return (matrix @ vec).astype(np.float32)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def batch_matmul(a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
118
|
+
"""General (M, K) @ (K, N) → (M, N) as np.float32.
|
|
119
|
+
|
|
120
|
+
Uses torch.mm on DEVICE when torch is available; falls back to numpy.
|
|
121
|
+
"""
|
|
122
|
+
if HAS_TORCH:
|
|
123
|
+
ta = to_t(a)
|
|
124
|
+
tb = to_t(b)
|
|
125
|
+
return torch.mm(ta, tb).cpu().numpy()
|
|
126
|
+
return (a @ b).astype(np.float32)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# MDA Dual Mode — SINGLE (numpy/CPU) vs BATCH (CUDA, high throughput)
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
class MDAMode:
|
|
134
|
+
SINGLE = "single"
|
|
135
|
+
BATCH = "batch"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_mode: str = MDAMode.SINGLE
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def set_mode(mode: str) -> None:
|
|
142
|
+
"""Switch between MDAMode.SINGLE and MDAMode.BATCH."""
|
|
143
|
+
global _mode
|
|
144
|
+
_mode = mode
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_mode() -> str:
|
|
148
|
+
"""Return the current mode string ('single' or 'batch')."""
|
|
149
|
+
return _mode
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def is_batch_mode() -> bool:
|
|
153
|
+
"""True only when mode is BATCH *and* CUDA is available."""
|
|
154
|
+
return _mode == MDAMode.BATCH and HAS_CUDA
|
|
@@ -1,39 +1,59 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
|
|
3
|
-
DIM =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
DIM = 512
|
|
4
|
+
|
|
5
|
+
# GPU delegation — safe regardless of torch availability.
|
|
6
|
+
try:
|
|
7
|
+
from mda.core.accelerator import (
|
|
8
|
+
normalize_t as _normalize_t,
|
|
9
|
+
cosine_t as _cosine_t,
|
|
10
|
+
fft_bind_t as _fft_bind_t,
|
|
11
|
+
HAS_TORCH,
|
|
12
|
+
)
|
|
13
|
+
except ImportError:
|
|
14
|
+
HAS_TORCH = False
|
|
15
|
+
_normalize_t = None
|
|
16
|
+
_cosine_t = None
|
|
17
|
+
_fft_bind_t = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def normalize(v: np.ndarray) -> np.ndarray:
|
|
21
|
+
if HAS_TORCH and _normalize_t is not None:
|
|
22
|
+
return _normalize_t(v)
|
|
23
|
+
n = np.linalg.norm(v)
|
|
24
|
+
return v / (n + 1e-8)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def random_vector(dim: int = DIM, seed: int = None) -> np.ndarray:
|
|
28
|
+
rng = np.random.default_rng(seed)
|
|
29
|
+
return normalize(rng.normal(0, 1, dim))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def zero_vector(dim: int = DIM) -> np.ndarray:
|
|
33
|
+
return np.zeros(dim)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def bind(a: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
37
|
+
if HAS_TORCH and _fft_bind_t is not None:
|
|
38
|
+
return _fft_bind_t(a, b)
|
|
39
|
+
return np.real(np.fft.ifft(np.fft.fft(a) * np.fft.fft(b)))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def unbind(compound: np.ndarray, b: np.ndarray) -> np.ndarray:
|
|
43
|
+
F_b = np.fft.fft(b)
|
|
44
|
+
F_b_inv = np.conj(F_b) / (np.abs(F_b) ** 2 + 1e-6)
|
|
45
|
+
b_inv = np.real(np.fft.ifft(F_b_inv))
|
|
46
|
+
return np.real(np.fft.ifft(np.fft.fft(compound) * np.fft.fft(b_inv)))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def bind_many(*vectors: np.ndarray) -> np.ndarray:
|
|
50
|
+
result = vectors[0].copy()
|
|
51
|
+
for v in vectors[1:]:
|
|
52
|
+
result = bind(result, v)
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cosine(a: np.ndarray, b: np.ndarray) -> float:
|
|
57
|
+
if HAS_TORCH and _cosine_t is not None:
|
|
58
|
+
return _cosine_t(a, b)
|
|
59
|
+
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-8))
|