retainr 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.
- retainr-0.2.0/.env.example +1 -0
- retainr-0.2.0/.github/workflows/ci.yml +34 -0
- retainr-0.2.0/.gitignore +12 -0
- retainr-0.2.0/.python-version +1 -0
- retainr-0.2.0/LICENSE +0 -0
- retainr-0.2.0/PKG-INFO +184 -0
- retainr-0.2.0/README.md +171 -0
- retainr-0.2.0/chatbot_memory.db +0 -0
- retainr-0.2.0/cli_memory.db +0 -0
- retainr-0.2.0/examples/basic_usage.py +28 -0
- retainr-0.2.0/examples/chatbot_with_memory.py +109 -0
- retainr-0.2.0/hello.py +6 -0
- retainr-0.2.0/image-1.png +0 -0
- retainr-0.2.0/image.png +0 -0
- retainr-0.2.0/memory.db +0 -0
- retainr-0.2.0/memoryos/__init__.py +4 -0
- retainr-0.2.0/memoryos/__main__.py +65 -0
- retainr-0.2.0/memoryos/embedder.py +11 -0
- retainr-0.2.0/memoryos/memory.py +38 -0
- retainr-0.2.0/memoryos/store.py +117 -0
- retainr-0.2.0/memoryos/utils.py +20 -0
- retainr-0.2.0/pyproject.toml +28 -0
- retainr-0.2.0/tests/test_memory.py +88 -0
- retainr-0.2.0/uv.lock +1781 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
HF_TOKEN=your_token_here
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v4
|
|
21
|
+
with:
|
|
22
|
+
version: "latest"
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
run: uv python install ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --all-extras --dev
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: uv run pytest tests/ -v
|
|
32
|
+
|
|
33
|
+
- name: Lint
|
|
34
|
+
run: uv run ruff check memoryos/
|
retainr-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
retainr-0.2.0/LICENSE
ADDED
|
File without changes
|
retainr-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: retainr
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Persistent, queryable AI memory for any Python app — local-first, zero API cost
|
|
5
|
+
Project-URL: Homepage, https://github.com/Devarajan8/memoryos
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: faiss-cpu>=1.8.0
|
|
10
|
+
Requires-Dist: groq>=1.2.0
|
|
11
|
+
Requires-Dist: sentence-transformers>=2.7.0
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# memoryos
|
|
15
|
+
|
|
16
|
+
[](https://github.com/Devarajan8/memoryos/actions)
|
|
17
|
+
|
|
18
|
+
> Persistent, queryable memory for any Python AI app — local-first, zero API cost.
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/memoryos-official/)
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
[](https://www.python.org/downloads/)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## What is it?
|
|
27
|
+
|
|
28
|
+
`memoryos` gives any AI app a long-term memory layer — without needing an API key, a cloud service, or a database server.
|
|
29
|
+
|
|
30
|
+
Store things. Recall them semantically. Forget them. All locally.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from memoryos import Memory
|
|
34
|
+
|
|
35
|
+
mem = Memory(user_id="arjun")
|
|
36
|
+
|
|
37
|
+
mem.remember("I prefer dark mode and use VS Code")
|
|
38
|
+
mem.remember("Currently building a job application assistant")
|
|
39
|
+
mem.remember("I like concise answers over long explanations")
|
|
40
|
+
|
|
41
|
+
results = mem.recall("what tools does the user prefer?")
|
|
42
|
+
for r in results:
|
|
43
|
+
print(f"[{r['score']:.3f}] {r['text']}")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
[0.412] I prefer dark mode and use VS Code
|
|
48
|
+
[0.289] I like concise answers over long explanations
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## CLI Demo
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
### Stats
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install memoryos-official
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **Semantic recall** — finds memories by meaning, not exact keywords
|
|
72
|
+
- **Memory decay** — old memories fade in relevance automatically
|
|
73
|
+
- **Importance scoring** — weight memories by how significant they are
|
|
74
|
+
- **Tags** — organize memories into namespaces
|
|
75
|
+
- **Forget & clear** — GDPR-friendly deletion
|
|
76
|
+
- **Local-first** — uses FAISS + SQLite, no external services
|
|
77
|
+
- **Zero cost** — no API keys, no cloud, runs on your machine
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## API Reference
|
|
82
|
+
|
|
83
|
+
### `Memory(user_id, db_path)`
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
mem = Memory(user_id="alice", db_path="memory.db")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Param | Default | Description |
|
|
90
|
+
| --------- | ------------- | ----------------------------------------------------- |
|
|
91
|
+
| `user_id` | `"default"` | Isolates memories per user |
|
|
92
|
+
| `db_path` | `"memory.db"` | SQLite file path. Use `":memory:"` for in-RAM (tests) |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### `remember(text, tags, importance, decay_days)`
|
|
97
|
+
|
|
98
|
+
Store a memory.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
mid = mem.remember(
|
|
102
|
+
"User just got promoted to SDE-2",
|
|
103
|
+
tags=["career", "milestone"],
|
|
104
|
+
importance=0.9, # 0.0–1.0, default 0.5
|
|
105
|
+
decay_days=90, # score halves every 90 days, 0 = no decay
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Returns: `str` — the memory ID (use it to `forget()` later)
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### `recall(query, top_k)`
|
|
114
|
+
|
|
115
|
+
Find the most semantically relevant memories.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
results = mem.recall("what is the user's job status?", top_k=3)
|
|
119
|
+
# [{"id": ..., "text": ..., "score": 0.87, "tags": [...], ...}]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `forget(memory_id)`
|
|
125
|
+
|
|
126
|
+
Delete a specific memory.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
mem.forget(mid)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### `clear()`
|
|
135
|
+
|
|
136
|
+
Wipe all memories for this user.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
mem.clear()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### `export(filepath)`
|
|
145
|
+
|
|
146
|
+
Backup all memories to JSON.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
mem.export("my_memories.json")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## How it works
|
|
155
|
+
|
|
156
|
+
1. **`remember()`** → text is embedded using `sentence-transformers` (all-MiniLM-L6-v2, runs locally) → vector stored in FAISS, metadata in SQLite
|
|
157
|
+
2. **`recall()`** → query is embedded → FAISS finds top-k nearest vectors → SQLite returns the original text
|
|
158
|
+
3. **Scoring** → final score = cosine similarity × (0.7 + 0.3 × decayed importance)
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Why not mem0 / Zep / etc.?
|
|
163
|
+
|
|
164
|
+
| | memoryos | mem0 | Zep |
|
|
165
|
+
| -------------- | -------- | ---------- | ---------- |
|
|
166
|
+
| Local-first | ✅ | ❌ | ❌ |
|
|
167
|
+
| API key needed | ❌ | ✅ | ✅ |
|
|
168
|
+
| Cost | Free | Paid tiers | Paid tiers |
|
|
169
|
+
| Memory decay | ✅ | ❌ | ❌ |
|
|
170
|
+
| `pip install` | ✅ | ✅ | ✅ |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Stack
|
|
175
|
+
|
|
176
|
+
- [`sentence-transformers`](https://www.sbert.net/) — local text embeddings
|
|
177
|
+
- [`faiss-cpu`](https://github.com/facebookresearch/faiss) — vector similarity search
|
|
178
|
+
- `sqlite3` — metadata storage (built into Python)
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT © Devarajan
|
retainr-0.2.0/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# memoryos
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Devarajan8/memoryos/actions)
|
|
4
|
+
|
|
5
|
+
> Persistent, queryable memory for any Python AI app — local-first, zero API cost.
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/memoryos-official/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.python.org/downloads/)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What is it?
|
|
14
|
+
|
|
15
|
+
`memoryos` gives any AI app a long-term memory layer — without needing an API key, a cloud service, or a database server.
|
|
16
|
+
|
|
17
|
+
Store things. Recall them semantically. Forget them. All locally.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from memoryos import Memory
|
|
21
|
+
|
|
22
|
+
mem = Memory(user_id="arjun")
|
|
23
|
+
|
|
24
|
+
mem.remember("I prefer dark mode and use VS Code")
|
|
25
|
+
mem.remember("Currently building a job application assistant")
|
|
26
|
+
mem.remember("I like concise answers over long explanations")
|
|
27
|
+
|
|
28
|
+
results = mem.recall("what tools does the user prefer?")
|
|
29
|
+
for r in results:
|
|
30
|
+
print(f"[{r['score']:.3f}] {r['text']}")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
[0.412] I prefer dark mode and use VS Code
|
|
35
|
+
[0.289] I like concise answers over long explanations
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## CLI Demo
|
|
39
|
+
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
### Stats
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install memoryos-official
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Semantic recall** — finds memories by meaning, not exact keywords
|
|
59
|
+
- **Memory decay** — old memories fade in relevance automatically
|
|
60
|
+
- **Importance scoring** — weight memories by how significant they are
|
|
61
|
+
- **Tags** — organize memories into namespaces
|
|
62
|
+
- **Forget & clear** — GDPR-friendly deletion
|
|
63
|
+
- **Local-first** — uses FAISS + SQLite, no external services
|
|
64
|
+
- **Zero cost** — no API keys, no cloud, runs on your machine
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### `Memory(user_id, db_path)`
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
mem = Memory(user_id="alice", db_path="memory.db")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Param | Default | Description |
|
|
77
|
+
| --------- | ------------- | ----------------------------------------------------- |
|
|
78
|
+
| `user_id` | `"default"` | Isolates memories per user |
|
|
79
|
+
| `db_path` | `"memory.db"` | SQLite file path. Use `":memory:"` for in-RAM (tests) |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### `remember(text, tags, importance, decay_days)`
|
|
84
|
+
|
|
85
|
+
Store a memory.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
mid = mem.remember(
|
|
89
|
+
"User just got promoted to SDE-2",
|
|
90
|
+
tags=["career", "milestone"],
|
|
91
|
+
importance=0.9, # 0.0–1.0, default 0.5
|
|
92
|
+
decay_days=90, # score halves every 90 days, 0 = no decay
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Returns: `str` — the memory ID (use it to `forget()` later)
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `recall(query, top_k)`
|
|
101
|
+
|
|
102
|
+
Find the most semantically relevant memories.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
results = mem.recall("what is the user's job status?", top_k=3)
|
|
106
|
+
# [{"id": ..., "text": ..., "score": 0.87, "tags": [...], ...}]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `forget(memory_id)`
|
|
112
|
+
|
|
113
|
+
Delete a specific memory.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
mem.forget(mid)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### `clear()`
|
|
122
|
+
|
|
123
|
+
Wipe all memories for this user.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
mem.clear()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### `export(filepath)`
|
|
132
|
+
|
|
133
|
+
Backup all memories to JSON.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
mem.export("my_memories.json")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## How it works
|
|
142
|
+
|
|
143
|
+
1. **`remember()`** → text is embedded using `sentence-transformers` (all-MiniLM-L6-v2, runs locally) → vector stored in FAISS, metadata in SQLite
|
|
144
|
+
2. **`recall()`** → query is embedded → FAISS finds top-k nearest vectors → SQLite returns the original text
|
|
145
|
+
3. **Scoring** → final score = cosine similarity × (0.7 + 0.3 × decayed importance)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Why not mem0 / Zep / etc.?
|
|
150
|
+
|
|
151
|
+
| | memoryos | mem0 | Zep |
|
|
152
|
+
| -------------- | -------- | ---------- | ---------- |
|
|
153
|
+
| Local-first | ✅ | ❌ | ❌ |
|
|
154
|
+
| API key needed | ❌ | ✅ | ✅ |
|
|
155
|
+
| Cost | Free | Paid tiers | Paid tiers |
|
|
156
|
+
| Memory decay | ✅ | ❌ | ❌ |
|
|
157
|
+
| `pip install` | ✅ | ✅ | ✅ |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Stack
|
|
162
|
+
|
|
163
|
+
- [`sentence-transformers`](https://www.sbert.net/) — local text embeddings
|
|
164
|
+
- [`faiss-cpu`](https://github.com/facebookresearch/faiss) — vector similarity search
|
|
165
|
+
- `sqlite3` — metadata storage (built into Python)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT © Devarajan
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from memoryos import Memory
|
|
2
|
+
|
|
3
|
+
mem = Memory(user_id="arjun")
|
|
4
|
+
mem.remember("I prefer dark mode and use VS Code")
|
|
5
|
+
mem.remember("Currently building memoryos, a local memory library")
|
|
6
|
+
mem.remember("I like concise answers over long explanations")
|
|
7
|
+
|
|
8
|
+
results = mem.recall("what is the user building?")
|
|
9
|
+
for r in results:
|
|
10
|
+
print(f"[{r['score']:.3f}] {r['text']}")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from memoryos import Memory
|
|
14
|
+
|
|
15
|
+
mem = Memory(user_id="arjun", db_path=":memory:")
|
|
16
|
+
mid = mem.remember("Old info", decay_days=1)
|
|
17
|
+
mem.remember("User prefers Python", importance=0.9)
|
|
18
|
+
mem.remember("User said hi", importance=0.1)
|
|
19
|
+
mem.remember("Wants dark UI", tags=["ui", "preference"])
|
|
20
|
+
|
|
21
|
+
results = mem.recall("what does the user prefer?", top_k=3)
|
|
22
|
+
for r in results:
|
|
23
|
+
print(f"[{r['score']:.3f}] {r['text']}")
|
|
24
|
+
|
|
25
|
+
mem.forget(mid)
|
|
26
|
+
print("After forget:", len(mem.recall("old info")), "results")
|
|
27
|
+
mem.clear()
|
|
28
|
+
print("After clear:", len(mem.recall("anything")), "results")
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
from memoryos import Memory
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
_env_file = Path(__file__).parent.parent / ".env"
|
|
8
|
+
if _env_file.exists():
|
|
9
|
+
for line in _env_file.read_text().splitlines():
|
|
10
|
+
line = line.strip()
|
|
11
|
+
if line and not line.startswith("#") and "=" in line:
|
|
12
|
+
key, _, value = line.partition("=")
|
|
13
|
+
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from groq import Groq
|
|
18
|
+
HAS_GROQ = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
HAS_GROQ = False
|
|
21
|
+
|
|
22
|
+
def get_memory_context(mem: Memory, user_message: str) -> str:
|
|
23
|
+
|
|
24
|
+
results = mem.recall(user_message, top_k=1)
|
|
25
|
+
if not results:
|
|
26
|
+
return ""
|
|
27
|
+
context = "\n".join(f"- {r['text']}" for r in results)
|
|
28
|
+
return f"\nRelevant things I remember about you:\n{context}\n"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def chat_with_memory():
|
|
32
|
+
mem = Memory(user_id="demo_user", db_path="chatbot_memory.db")
|
|
33
|
+
print("Chatbot with memory (type 'quit' to exit, 'forget all' to clear)")
|
|
34
|
+
print("\n")
|
|
35
|
+
|
|
36
|
+
if HAS_GROQ:
|
|
37
|
+
client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
|
|
38
|
+
else:
|
|
39
|
+
print(" Groq not installed — running in echo mode (no LLM)")
|
|
40
|
+
print(" Install: pip install groq")
|
|
41
|
+
print(" Get free API key: console.groq.com")
|
|
42
|
+
print()
|
|
43
|
+
|
|
44
|
+
conversation_history = []
|
|
45
|
+
|
|
46
|
+
while True:
|
|
47
|
+
user_input = input("You: ").strip()
|
|
48
|
+
if not user_input:
|
|
49
|
+
continue
|
|
50
|
+
if user_input.lower() == "quit":
|
|
51
|
+
break
|
|
52
|
+
if user_input.lower() == "forget all":
|
|
53
|
+
mem.clear()
|
|
54
|
+
print("Bot: Memory cleared.\n")
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
def should_remember(user_input: str, client) -> bool:
|
|
58
|
+
|
|
59
|
+
response = client.chat.completions.create(
|
|
60
|
+
model="llama-3.3-70b-versatile",
|
|
61
|
+
messages=[{
|
|
62
|
+
"role": "user",
|
|
63
|
+
"content": (
|
|
64
|
+
f"Does this message contain a personal fact, preference, or "
|
|
65
|
+
f"long-term information worth remembering about the user?\n\n"
|
|
66
|
+
f"Message: \"{user_input}\"\n\n"
|
|
67
|
+
f"Reply with only YES or NO."
|
|
68
|
+
)
|
|
69
|
+
}],
|
|
70
|
+
max_tokens=5,
|
|
71
|
+
temperature=0,
|
|
72
|
+
)
|
|
73
|
+
answer = response.choices[0].message.content.strip().upper()
|
|
74
|
+
return answer.startswith("YES")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if HAS_GROQ and should_remember(user_input, client):
|
|
78
|
+
mem.remember(user_input, importance=0.8)
|
|
79
|
+
print(" Stored in memory")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
memory_context = get_memory_context(mem, user_input)
|
|
83
|
+
|
|
84
|
+
if HAS_GROQ:
|
|
85
|
+
system_prompt = (
|
|
86
|
+
"You are a helpful assistant with memory. "
|
|
87
|
+
"Use the remembered context to personalise your responses."
|
|
88
|
+
+ memory_context
|
|
89
|
+
)
|
|
90
|
+
conversation_history.append({"role": "user", "content": user_input})
|
|
91
|
+
response = client.chat.completions.create(
|
|
92
|
+
model="llama-3.3-70b-versatile",
|
|
93
|
+
messages=[{"role": "system", "content": system_prompt}]
|
|
94
|
+
+ conversation_history[-6:],
|
|
95
|
+
)
|
|
96
|
+
reply = response.choices[0].message.content
|
|
97
|
+
conversation_history.append({"role": "assistant", "content": reply})
|
|
98
|
+
print(f"Bot: {reply}\n")
|
|
99
|
+
else:
|
|
100
|
+
# Demo mode without LLM
|
|
101
|
+
if memory_context:
|
|
102
|
+
print(f"Bot: [Echo + Memory] {user_input}")
|
|
103
|
+
print(f" Context I have:{memory_context}")
|
|
104
|
+
else:
|
|
105
|
+
print(f"Bot: [Echo] {user_input}\n")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
chat_with_memory()
|
retainr-0.2.0/hello.py
ADDED
|
Binary file
|
retainr-0.2.0/image.png
ADDED
|
Binary file
|
retainr-0.2.0/memory.db
ADDED
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
import sys
|
|
3
|
+
from memoryos import Memory, __version__
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_env_file = Path(__file__).parent.parent / ".env"
|
|
10
|
+
if _env_file.exists():
|
|
11
|
+
for line in _env_file.read_text().splitlines():
|
|
12
|
+
line = line.strip()
|
|
13
|
+
if line and not line.startswith("#") and "=" in line:
|
|
14
|
+
key, _, value = line.partition("=")
|
|
15
|
+
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
print(f"memoryos v{__version__} — local AI memory")
|
|
20
|
+
print("Commands: remember <text> | recall <query> | clear | stats")
|
|
21
|
+
print()
|
|
22
|
+
|
|
23
|
+
mem = Memory(user_id="cli_user", db_path="cli_memory.db")
|
|
24
|
+
|
|
25
|
+
if len(sys.argv) < 2:
|
|
26
|
+
print("Usage: python -m memoryos remember 'some text'")
|
|
27
|
+
print(" python -m memoryos recall 'your query'")
|
|
28
|
+
print(" python -m memoryos clear")
|
|
29
|
+
print(" python -m memoryos stats")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
command = sys.argv[1].lower()
|
|
33
|
+
args = " ".join(sys.argv[2:])
|
|
34
|
+
|
|
35
|
+
if command == "remember":
|
|
36
|
+
if not args:
|
|
37
|
+
print("Error: provide text to remember")
|
|
38
|
+
return
|
|
39
|
+
mid = mem.remember(args)
|
|
40
|
+
print(f"Remembered (id: {mid[:8]}...)")
|
|
41
|
+
|
|
42
|
+
elif command == "recall":
|
|
43
|
+
if not args:
|
|
44
|
+
print("Error: provide a query")
|
|
45
|
+
return
|
|
46
|
+
results = mem.recall(args, top_k=1)
|
|
47
|
+
if not results:
|
|
48
|
+
print("No memories found.")
|
|
49
|
+
for r in results:
|
|
50
|
+
print(f"[{r['score']:.3f}] {r['text']}")
|
|
51
|
+
|
|
52
|
+
elif command == "clear":
|
|
53
|
+
mem.clear()
|
|
54
|
+
print(" All memories cleared.")
|
|
55
|
+
|
|
56
|
+
elif command == "stats":
|
|
57
|
+
results = mem.recall("", top_k=99999)
|
|
58
|
+
print(f"Total memories: {len(results)}")
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
print(f"Unknown command: {command}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from sentence_transformers import SentenceTransformer
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
class Embedder:
|
|
5
|
+
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
|
|
6
|
+
|
|
7
|
+
self.model = SentenceTransformer(model_name)
|
|
8
|
+
|
|
9
|
+
def encode(self, text: str) -> np.ndarray:
|
|
10
|
+
|
|
11
|
+
return self.model.encode(text, normalize_embeddings=True)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from .embedder import Embedder
|
|
3
|
+
from .store import VectorStore
|
|
4
|
+
|
|
5
|
+
class Memory:
|
|
6
|
+
def __init__(self, user_id: str = "default", db_path: str = "memory.db"):
|
|
7
|
+
self.user_id = user_id
|
|
8
|
+
self.embedder = Embedder()
|
|
9
|
+
self.store = VectorStore(db_path)
|
|
10
|
+
|
|
11
|
+
def forget(self, memory_id: str) -> bool:
|
|
12
|
+
return self.store.delete(memory_id)
|
|
13
|
+
|
|
14
|
+
def clear(self):
|
|
15
|
+
self.store.clear(self.user_id)
|
|
16
|
+
|
|
17
|
+
def export(self, filepath: str):
|
|
18
|
+
import json
|
|
19
|
+
# Get all memories (high top_k to get everything)
|
|
20
|
+
results = self.store.search(self.user_id,
|
|
21
|
+
self.embedder.encode(""), top_k=99999)
|
|
22
|
+
with open(filepath, "w") as f:
|
|
23
|
+
json.dump(results, f, indent=2)
|
|
24
|
+
|
|
25
|
+
def remember(self, text: str, tags: list[str] = [],
|
|
26
|
+
importance: float = 0.5, decay_days: int = 0) -> str:
|
|
27
|
+
|
|
28
|
+
embedding = self.embedder.encode(text)
|
|
29
|
+
return self.store.save(
|
|
30
|
+
user_id=self.user_id, text=text, embedding=embedding,
|
|
31
|
+
tags=tags, timestamp=time.time(),
|
|
32
|
+
importance=importance, decay_days=decay_days,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def recall(self, query: str, top_k: int = 1) -> list[dict]:
|
|
36
|
+
|
|
37
|
+
query_embedding = self.embedder.encode(query)
|
|
38
|
+
return self.store.search(self.user_id, query_embedding, top_k)
|