memloop 0.1.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.
- memloop-0.1.0/PKG-INFO +199 -0
- memloop-0.1.0/README.md +171 -0
- memloop-0.1.0/memloop/__init__.py +7 -0
- memloop-0.1.0/memloop/brain.py +138 -0
- memloop-0.1.0/memloop/cli.py +80 -0
- memloop-0.1.0/memloop/file_loader.py +89 -0
- memloop-0.1.0/memloop/storage.py +45 -0
- memloop-0.1.0/memloop/web_reader.py +34 -0
- memloop-0.1.0/memloop.egg-info/PKG-INFO +199 -0
- memloop-0.1.0/memloop.egg-info/SOURCES.txt +14 -0
- memloop-0.1.0/memloop.egg-info/dependency_links.txt +1 -0
- memloop-0.1.0/memloop.egg-info/entry_points.txt +2 -0
- memloop-0.1.0/memloop.egg-info/requires.txt +5 -0
- memloop-0.1.0/memloop.egg-info/top_level.txt +1 -0
- memloop-0.1.0/setup.cfg +4 -0
- memloop-0.1.0/setup.py +37 -0
memloop-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memloop
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A local-first, dual-memory engine for AI Agents.
|
|
5
|
+
Home-page: https://github.com/vanshcodeworks/memloop
|
|
6
|
+
Author: Vansh
|
|
7
|
+
Author-email: vanshgoyal9528@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: chromadb
|
|
15
|
+
Requires-Dist: sentence-transformers
|
|
16
|
+
Requires-Dist: beautifulsoup4
|
|
17
|
+
Requires-Dist: requests
|
|
18
|
+
Requires-Dist: pypdf
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# MemLoop: Local Vector Memory for AI Agents
|
|
30
|
+
|
|
31
|
+
> **"Give your AI infinite memory without the API bills."**
|
|
32
|
+
|
|
33
|
+
**MemLoop** is a production-ready, local-first memory orchestration engine designed to give LLMs (like Gemini, GPT-4, Llama 3) long-term retention capabilities. It bridges the gap between transient context windows and persistent vector storage.
|
|
34
|
+
|
|
35
|
+
Unlike wrapper libraries, MemLoop implements a custom **Dual-Memory Architecture** (Short-term buffer + Long-term Vector Store) and runs entirely offline.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Why MemLoop?
|
|
40
|
+
|
|
41
|
+
* **Privacy-First & Offline:** Runs 100% locally using `ChromaDB` and `SentenceTransformers`. No OpenAI API keys required. Your data never leaves your machine.
|
|
42
|
+
* **Zero-Latency Caching:** Implements an **O(1) Semantic Cache** that intercepts repeated queries before they hit the Vector DB, reducing retrieval latency by ~99%.
|
|
43
|
+
* **Citation-Aware Retrieval:** Don't just get text; get the source. MemLoop tracks **Page Numbers, Row Indices, and URLs** so your AI can cite its sources (e.g., *"Source: manual.pdf, Page 12"*).
|
|
44
|
+
* **Universal Ingestion:** Built-in ETL pipeline that automatically ingests:
|
|
45
|
+
* **Websites** (Recursive crawling with `BeautifulSoup`)
|
|
46
|
+
* **PDFs & Docs** (Automatic text chunking)
|
|
47
|
+
* **Tabular Data** (CSV/Excel linearizer for vector compatibility)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
MemLoop decouples ingestion from retrieval, using a hybrid cache-first strategy to ensure speed and accuracy.
|
|
56
|
+
|
|
57
|
+
```mermaid
|
|
58
|
+
graph TD
|
|
59
|
+
subgraph MemLoop_Engine [MemLoop Core]
|
|
60
|
+
Query[User Query] --> Cache{Check Cache?}
|
|
61
|
+
Cache -- Hit (0.01ms) --> Response
|
|
62
|
+
Cache -- Miss --> VectorDB[(ChromaDB <br/> Local Store)]
|
|
63
|
+
VectorDB --> Rerank[Context Reranker]
|
|
64
|
+
Rerank --> Response
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subgraph Ingestion_Layer [ETL Pipeline]
|
|
68
|
+
Web[Web Scraper] --> Chunker
|
|
69
|
+
Files[PDF/CSV Loader] --> Chunker
|
|
70
|
+
Chunker --> Embed[Local Embeddings]
|
|
71
|
+
Embed --> VectorDB
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
### 1. Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install memloop
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. The Interactive CLI (Chat with your Data)
|
|
88
|
+
|
|
89
|
+
Launch the built-in terminal interface to test your memory engine instantly.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
$ memloop
|
|
93
|
+
|
|
94
|
+
[SYSTEM]: Initializing Neural Link...
|
|
95
|
+
[USER]: /learn https://en.wikipedia.org/wiki/Artificial_intelligence
|
|
96
|
+
[SYSTEM]: Success. Absorbed 45 chunks.
|
|
97
|
+
[USER]: What is AI?
|
|
98
|
+
[MEMLOOP]: "AI is intelligence demonstrated by machines..." (Source: Wikipedia, Chunk 12)
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Python SDK (Build your own Agent)
|
|
103
|
+
|
|
104
|
+
Integrate MemLoop into your Python projects in 3 lines of code.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from memloop import MemLoop
|
|
108
|
+
|
|
109
|
+
# Initialize Brain (Persists to ./memloop_data)
|
|
110
|
+
brain = MemLoop()
|
|
111
|
+
|
|
112
|
+
# A. Ingest Knowledge
|
|
113
|
+
print("Ingesting documentation...")
|
|
114
|
+
brain.learn_url("https://docs.python.org/3/")
|
|
115
|
+
brain.learn_local("./my_documents_folder")
|
|
116
|
+
|
|
117
|
+
# B. Add Conversation Context
|
|
118
|
+
brain.add_memory("User is building a React app.")
|
|
119
|
+
|
|
120
|
+
# C. Retrieve Context (with Caching & Citations)
|
|
121
|
+
context = brain.recall("How do python decorators work?")
|
|
122
|
+
|
|
123
|
+
print(context)
|
|
124
|
+
# Output:
|
|
125
|
+
# "Short Term: User is building a React app..."
|
|
126
|
+
# "Long Term: [1] Decorators are functions... (Ref: python.org, Section 4.2)"
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Integration Example: MemLoop + Gemini
|
|
133
|
+
|
|
134
|
+
Here is how to use MemLoop as the "Long-Term Memory" for a Gemini (or OpenAI) agent.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import google.generativeai as genai
|
|
138
|
+
from memloop import MemLoop
|
|
139
|
+
|
|
140
|
+
# 1. Setup Memory
|
|
141
|
+
brain = MemLoop()
|
|
142
|
+
|
|
143
|
+
# 2. Setup LLM
|
|
144
|
+
genai.configure(api_key="YOUR_API_KEY")
|
|
145
|
+
model = genai.GenerativeModel('gemini-2.5-flash')
|
|
146
|
+
|
|
147
|
+
def ask_agent(query):
|
|
148
|
+
# Retrieve relevant memories locally (Free & Fast)
|
|
149
|
+
context = brain.recall(query)
|
|
150
|
+
|
|
151
|
+
# Send only relevant context to LLM
|
|
152
|
+
prompt = f"""
|
|
153
|
+
Use the following context to answer the user.
|
|
154
|
+
Context: {context}
|
|
155
|
+
|
|
156
|
+
User: {query}
|
|
157
|
+
Answer:
|
|
158
|
+
"""
|
|
159
|
+
response = model.generate_content(prompt)
|
|
160
|
+
return response.text
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Supported Formats
|
|
167
|
+
|
|
168
|
+
| Format | Features |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| **.txt / .md** | Standard text chunking with overlap. |
|
|
171
|
+
| **.csv** | **Row Linearization**: Converts rows into narrative sentences for better vector matching. |
|
|
172
|
+
| **.pdf** | **Page Tracking**: extracts text while preserving page numbers for citations. |
|
|
173
|
+
| **URLs** | **Smart Scraper**: Auto-removes HTML boilerplate (scripts, navbars, ads). |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🗺️ Roadmap
|
|
178
|
+
|
|
179
|
+
* [x] Local Vector Storage (ChromaDB)
|
|
180
|
+
* [x] Semantic Caching (LRU Strategy)
|
|
181
|
+
* [x] Web & Local File Ingestion
|
|
182
|
+
* [ ] Multi-Modal Support (Image Embeddings)
|
|
183
|
+
* [ ] GraphRAG Integration (Knowledge Graphs)
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Contributing
|
|
188
|
+
|
|
189
|
+
Contributions are welcome! Please open an issue or submit a PR.
|
|
190
|
+
|
|
191
|
+
1. Fork the repo
|
|
192
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
193
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
194
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
195
|
+
5. Open a Pull Request
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
**Built with ❤️ by [Vansh**](https://github.com/vanshcodeworks)
|
memloop-0.1.0/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# MemLoop: Local Vector Memory for AI Agents
|
|
2
|
+
|
|
3
|
+
> **"Give your AI infinite memory without the API bills."**
|
|
4
|
+
|
|
5
|
+
**MemLoop** is a production-ready, local-first memory orchestration engine designed to give LLMs (like Gemini, GPT-4, Llama 3) long-term retention capabilities. It bridges the gap between transient context windows and persistent vector storage.
|
|
6
|
+
|
|
7
|
+
Unlike wrapper libraries, MemLoop implements a custom **Dual-Memory Architecture** (Short-term buffer + Long-term Vector Store) and runs entirely offline.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why MemLoop?
|
|
12
|
+
|
|
13
|
+
* **Privacy-First & Offline:** Runs 100% locally using `ChromaDB` and `SentenceTransformers`. No OpenAI API keys required. Your data never leaves your machine.
|
|
14
|
+
* **Zero-Latency Caching:** Implements an **O(1) Semantic Cache** that intercepts repeated queries before they hit the Vector DB, reducing retrieval latency by ~99%.
|
|
15
|
+
* **Citation-Aware Retrieval:** Don't just get text; get the source. MemLoop tracks **Page Numbers, Row Indices, and URLs** so your AI can cite its sources (e.g., *"Source: manual.pdf, Page 12"*).
|
|
16
|
+
* **Universal Ingestion:** Built-in ETL pipeline that automatically ingests:
|
|
17
|
+
* **Websites** (Recursive crawling with `BeautifulSoup`)
|
|
18
|
+
* **PDFs & Docs** (Automatic text chunking)
|
|
19
|
+
* **Tabular Data** (CSV/Excel linearizer for vector compatibility)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Architecture
|
|
26
|
+
|
|
27
|
+
MemLoop decouples ingestion from retrieval, using a hybrid cache-first strategy to ensure speed and accuracy.
|
|
28
|
+
|
|
29
|
+
```mermaid
|
|
30
|
+
graph TD
|
|
31
|
+
subgraph MemLoop_Engine [MemLoop Core]
|
|
32
|
+
Query[User Query] --> Cache{Check Cache?}
|
|
33
|
+
Cache -- Hit (0.01ms) --> Response
|
|
34
|
+
Cache -- Miss --> VectorDB[(ChromaDB <br/> Local Store)]
|
|
35
|
+
VectorDB --> Rerank[Context Reranker]
|
|
36
|
+
Rerank --> Response
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
subgraph Ingestion_Layer [ETL Pipeline]
|
|
40
|
+
Web[Web Scraper] --> Chunker
|
|
41
|
+
Files[PDF/CSV Loader] --> Chunker
|
|
42
|
+
Chunker --> Embed[Local Embeddings]
|
|
43
|
+
Embed --> VectorDB
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
### 1. Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install memloop
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. The Interactive CLI (Chat with your Data)
|
|
60
|
+
|
|
61
|
+
Launch the built-in terminal interface to test your memory engine instantly.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
$ memloop
|
|
65
|
+
|
|
66
|
+
[SYSTEM]: Initializing Neural Link...
|
|
67
|
+
[USER]: /learn https://en.wikipedia.org/wiki/Artificial_intelligence
|
|
68
|
+
[SYSTEM]: Success. Absorbed 45 chunks.
|
|
69
|
+
[USER]: What is AI?
|
|
70
|
+
[MEMLOOP]: "AI is intelligence demonstrated by machines..." (Source: Wikipedia, Chunk 12)
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Python SDK (Build your own Agent)
|
|
75
|
+
|
|
76
|
+
Integrate MemLoop into your Python projects in 3 lines of code.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from memloop import MemLoop
|
|
80
|
+
|
|
81
|
+
# Initialize Brain (Persists to ./memloop_data)
|
|
82
|
+
brain = MemLoop()
|
|
83
|
+
|
|
84
|
+
# A. Ingest Knowledge
|
|
85
|
+
print("Ingesting documentation...")
|
|
86
|
+
brain.learn_url("https://docs.python.org/3/")
|
|
87
|
+
brain.learn_local("./my_documents_folder")
|
|
88
|
+
|
|
89
|
+
# B. Add Conversation Context
|
|
90
|
+
brain.add_memory("User is building a React app.")
|
|
91
|
+
|
|
92
|
+
# C. Retrieve Context (with Caching & Citations)
|
|
93
|
+
context = brain.recall("How do python decorators work?")
|
|
94
|
+
|
|
95
|
+
print(context)
|
|
96
|
+
# Output:
|
|
97
|
+
# "Short Term: User is building a React app..."
|
|
98
|
+
# "Long Term: [1] Decorators are functions... (Ref: python.org, Section 4.2)"
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Integration Example: MemLoop + Gemini
|
|
105
|
+
|
|
106
|
+
Here is how to use MemLoop as the "Long-Term Memory" for a Gemini (or OpenAI) agent.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import google.generativeai as genai
|
|
110
|
+
from memloop import MemLoop
|
|
111
|
+
|
|
112
|
+
# 1. Setup Memory
|
|
113
|
+
brain = MemLoop()
|
|
114
|
+
|
|
115
|
+
# 2. Setup LLM
|
|
116
|
+
genai.configure(api_key="YOUR_API_KEY")
|
|
117
|
+
model = genai.GenerativeModel('gemini-2.5-flash')
|
|
118
|
+
|
|
119
|
+
def ask_agent(query):
|
|
120
|
+
# Retrieve relevant memories locally (Free & Fast)
|
|
121
|
+
context = brain.recall(query)
|
|
122
|
+
|
|
123
|
+
# Send only relevant context to LLM
|
|
124
|
+
prompt = f"""
|
|
125
|
+
Use the following context to answer the user.
|
|
126
|
+
Context: {context}
|
|
127
|
+
|
|
128
|
+
User: {query}
|
|
129
|
+
Answer:
|
|
130
|
+
"""
|
|
131
|
+
response = model.generate_content(prompt)
|
|
132
|
+
return response.text
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Supported Formats
|
|
139
|
+
|
|
140
|
+
| Format | Features |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| **.txt / .md** | Standard text chunking with overlap. |
|
|
143
|
+
| **.csv** | **Row Linearization**: Converts rows into narrative sentences for better vector matching. |
|
|
144
|
+
| **.pdf** | **Page Tracking**: extracts text while preserving page numbers for citations. |
|
|
145
|
+
| **URLs** | **Smart Scraper**: Auto-removes HTML boilerplate (scripts, navbars, ads). |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 🗺️ Roadmap
|
|
150
|
+
|
|
151
|
+
* [x] Local Vector Storage (ChromaDB)
|
|
152
|
+
* [x] Semantic Caching (LRU Strategy)
|
|
153
|
+
* [x] Web & Local File Ingestion
|
|
154
|
+
* [ ] Multi-Modal Support (Image Embeddings)
|
|
155
|
+
* [ ] GraphRAG Integration (Knowledge Graphs)
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Contributing
|
|
160
|
+
|
|
161
|
+
Contributions are welcome! Please open an issue or submit a PR.
|
|
162
|
+
|
|
163
|
+
1. Fork the repo
|
|
164
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
165
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
166
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
167
|
+
5. Open a Pull Request
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
**Built with ❤️ by [Vansh**](https://github.com/vanshcodeworks)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Core orchestration layer that ties storage, caching, and retrieval."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from .storage import LocalMemory
|
|
5
|
+
from .web_reader import crawl_and_extract
|
|
6
|
+
from .file_loader import ingest_folder, load_text_file, load_pdf_pages
|
|
7
|
+
|
|
8
|
+
class MemLoop:
|
|
9
|
+
"""Plug-and-play memory engine for AI agents."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, db_path="./memloop_data"):
|
|
12
|
+
self.memory = LocalMemory(path=db_path)
|
|
13
|
+
self.short_term: list[str] = []
|
|
14
|
+
self.cache: dict[str, str] = {}
|
|
15
|
+
|
|
16
|
+
# ── helpers ───────────────────────────────────────────
|
|
17
|
+
def _hash(self, text: str) -> str:
|
|
18
|
+
"""Create a unique hash for semantic caching."""
|
|
19
|
+
return hashlib.md5(text.encode()).hexdigest()
|
|
20
|
+
|
|
21
|
+
def _chunk_text(self, text: str, chunk_size: int = 500):
|
|
22
|
+
return [text[i : i + chunk_size] for i in range(0, len(text), chunk_size)]
|
|
23
|
+
|
|
24
|
+
# ── ingestion ─────────────────────────────────────────
|
|
25
|
+
def learn_url(self, url: str) -> int:
|
|
26
|
+
"""Scrape *url*, chunk it, and save every chunk. Returns chunk count."""
|
|
27
|
+
self.cache.clear()
|
|
28
|
+
chunks = crawl_and_extract(url)
|
|
29
|
+
count = 0
|
|
30
|
+
for chunk in chunks:
|
|
31
|
+
if chunk and chunk.strip():
|
|
32
|
+
self.memory.save(chunk, metadata={"source": url, "type": "web"})
|
|
33
|
+
count += 1
|
|
34
|
+
return count
|
|
35
|
+
|
|
36
|
+
def learn_local(self, folder_path: str) -> int:
|
|
37
|
+
"""Ingest all supported files from a local folder."""
|
|
38
|
+
self.cache.clear()
|
|
39
|
+
docs = ingest_folder(folder_path)
|
|
40
|
+
count = 0
|
|
41
|
+
|
|
42
|
+
for text, meta in docs:
|
|
43
|
+
for idx, chunk in enumerate(self._chunk_text(text)):
|
|
44
|
+
if chunk.strip():
|
|
45
|
+
meta_with_chunk = {**meta, "chunk_index": idx}
|
|
46
|
+
self.memory.save(chunk, metadata=meta_with_chunk)
|
|
47
|
+
count += 1
|
|
48
|
+
return count
|
|
49
|
+
|
|
50
|
+
def learn_doc(self, file_path: str, page_number: int | None = None) -> int:
|
|
51
|
+
"""Ingest a specific document (or a specific page for PDFs)."""
|
|
52
|
+
self.cache.clear()
|
|
53
|
+
count = 0
|
|
54
|
+
|
|
55
|
+
if file_path.lower().endswith(".pdf"):
|
|
56
|
+
pages = load_pdf_pages(file_path)
|
|
57
|
+
for text, meta in pages:
|
|
58
|
+
if page_number and meta.get("page") != page_number:
|
|
59
|
+
continue
|
|
60
|
+
for idx, chunk in enumerate(self._chunk_text(text)):
|
|
61
|
+
if chunk.strip():
|
|
62
|
+
meta_with_chunk = {**meta, "chunk_index": idx}
|
|
63
|
+
self.memory.save(chunk, metadata=meta_with_chunk)
|
|
64
|
+
count += 1
|
|
65
|
+
return count
|
|
66
|
+
|
|
67
|
+
content = load_text_file(file_path)
|
|
68
|
+
for idx, chunk in enumerate(self._chunk_text(content)):
|
|
69
|
+
if chunk.strip():
|
|
70
|
+
meta = {"source": file_path, "type": "text", "page": 1, "chunk_index": idx}
|
|
71
|
+
self.memory.save(chunk, metadata=meta)
|
|
72
|
+
count += 1
|
|
73
|
+
return count
|
|
74
|
+
|
|
75
|
+
def add_memory(self, text: str) -> None:
|
|
76
|
+
"""Store *text* in both long-term vector DB and short-term buffer."""
|
|
77
|
+
# 1. Invalidate cache so we don't return old/stale answers
|
|
78
|
+
self.cache.clear()
|
|
79
|
+
|
|
80
|
+
# 2. Save to Vector Store (Long Term)
|
|
81
|
+
self.memory.save(text, metadata={"type": "user_input"})
|
|
82
|
+
|
|
83
|
+
# 3. Update Working Memory (Short Term)
|
|
84
|
+
self.short_term.append(text)
|
|
85
|
+
if len(self.short_term) > 5:
|
|
86
|
+
self.short_term.pop(0)
|
|
87
|
+
|
|
88
|
+
# ── retrieval ─────────────────────────────────────────
|
|
89
|
+
def recall(self, query: str, n_results: int = 3) -> str:
|
|
90
|
+
"""Retrieve context for *query*. Uses cache when possible."""
|
|
91
|
+
key = self._hash(query)
|
|
92
|
+
|
|
93
|
+
# 1. Check Cache (Speed Optimization)
|
|
94
|
+
if key in self.cache:
|
|
95
|
+
return f"[CACHE HIT] {self.cache[key]}"
|
|
96
|
+
|
|
97
|
+
documents, metadatas = self.memory.search_with_meta(query, n_results=n_results)
|
|
98
|
+
pairs = [
|
|
99
|
+
(doc, meta)
|
|
100
|
+
for doc, meta in zip(documents, metadatas)
|
|
101
|
+
if doc.strip().lower() != query.strip().lower()
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
response = "Found References:\n"
|
|
105
|
+
for i, (doc, meta) in enumerate(pairs, start=1):
|
|
106
|
+
source = meta.get("source", "unknown")
|
|
107
|
+
page = meta.get("page", "?")
|
|
108
|
+
response += f"[{i}] {doc[:150]}... (Ref: {source}, Page {page})\n"
|
|
109
|
+
|
|
110
|
+
self.cache[key] = response
|
|
111
|
+
return response
|
|
112
|
+
|
|
113
|
+
# ── management ────────────────────────────────────────
|
|
114
|
+
def forget_cache(self) -> None:
|
|
115
|
+
"""Clear the semantic cache."""
|
|
116
|
+
self.cache.clear()
|
|
117
|
+
|
|
118
|
+
def status(self) -> dict:
|
|
119
|
+
"""Return a snapshot of the memory state."""
|
|
120
|
+
try:
|
|
121
|
+
# Depending on ChromaDB version, .count() is usually available on the collection
|
|
122
|
+
lt_count = self.memory.collection.count()
|
|
123
|
+
except AttributeError:
|
|
124
|
+
lt_count = "Unknown"
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"long_term_count": lt_count,
|
|
128
|
+
"short_term_count": len(self.short_term),
|
|
129
|
+
"cache_size": len(self.cache),
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
def __repr__(self) -> str:
|
|
133
|
+
s = self.status()
|
|
134
|
+
return (
|
|
135
|
+
f"MemLoop(long_term={s['long_term_count']}, "
|
|
136
|
+
f"short_term={s['short_term_count']}, "
|
|
137
|
+
f"cache={s['cache_size']})"
|
|
138
|
+
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import time
|
|
3
|
+
from .brain import MemLoop
|
|
4
|
+
|
|
5
|
+
def type_writer(text, speed=0.02):
|
|
6
|
+
for char in text:
|
|
7
|
+
sys.stdout.write(char)
|
|
8
|
+
sys.stdout.flush()
|
|
9
|
+
time.sleep(speed)
|
|
10
|
+
print("")
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
print("\n" + "=" * 40)
|
|
14
|
+
print(" MEMLOOP v0.1.0 - Local Vector Memory")
|
|
15
|
+
print("=" * 40)
|
|
16
|
+
print("Initializing...")
|
|
17
|
+
|
|
18
|
+
agent = MemLoop()
|
|
19
|
+
|
|
20
|
+
print("\ncommands:")
|
|
21
|
+
print(" /learn <url> -> Ingest a website into Long-Term Memory")
|
|
22
|
+
print(" /status -> Show memory stats")
|
|
23
|
+
print(" /forget -> Clear semantic cache")
|
|
24
|
+
print(" /exit -> Close the session")
|
|
25
|
+
print(" <text> -> Chat/Add to Memory")
|
|
26
|
+
print("-" * 40 + "\n")
|
|
27
|
+
|
|
28
|
+
while True:
|
|
29
|
+
try:
|
|
30
|
+
user_input = input("\n[USER]: ").strip()
|
|
31
|
+
|
|
32
|
+
if not user_input:
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
if user_input.lower() == "/exit":
|
|
36
|
+
type_writer("[SYSTEM]: Shutting down memory core. Goodbye.")
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
elif user_input.lower() == "/status":
|
|
40
|
+
print(f"[SYSTEM]: {agent.status()}")
|
|
41
|
+
|
|
42
|
+
elif user_input.lower() == "/forget":
|
|
43
|
+
agent.forget_cache()
|
|
44
|
+
type_writer("[SYSTEM]: Semantic cache cleared.")
|
|
45
|
+
|
|
46
|
+
elif user_input.startswith("/learn "):
|
|
47
|
+
url = user_input.split(" ", 1)[1]
|
|
48
|
+
type_writer(f"[SYSTEM]: Deploying spider to {url}...")
|
|
49
|
+
try:
|
|
50
|
+
count = agent.learn_url(url)
|
|
51
|
+
type_writer(f"[SYSTEM]: Success. Absorbed {count} knowledge chunks.")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
type_writer(f"[ERROR]: Failed to ingest. {e}")
|
|
54
|
+
|
|
55
|
+
elif user_input.startswith("/read "):
|
|
56
|
+
path = user_input.split(" ", 1)[1].strip()
|
|
57
|
+
type_writer(f"[SYSTEM]: Ingesting local data from {path}...")
|
|
58
|
+
try:
|
|
59
|
+
count = agent.learn_local(path)
|
|
60
|
+
type_writer(f"[SYSTEM]: Success. Indexed {count} documents/rows.")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
type_writer(f"[ERROR]: Could not read path. {e}")
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
agent.add_memory(user_input)
|
|
66
|
+
type_writer("[SYSTEM]: Searching Vector Space...")
|
|
67
|
+
response = agent.recall(user_input)
|
|
68
|
+
|
|
69
|
+
print("\n[MEMLOOP KNOWLEDGE GRAPH]:")
|
|
70
|
+
print("-" * 40)
|
|
71
|
+
print(response)
|
|
72
|
+
print("-" * 40)
|
|
73
|
+
print("Tip: Use sources to verify facts.\n")
|
|
74
|
+
|
|
75
|
+
except KeyboardInterrupt:
|
|
76
|
+
print("\n[SYSTEM]: Force quit detected.")
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import csv
|
|
3
|
+
import json
|
|
4
|
+
from pypdf import PdfReader
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_text_file(filepath):
|
|
8
|
+
"""Reads .txt or .md files."""
|
|
9
|
+
try:
|
|
10
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
11
|
+
return f.read()
|
|
12
|
+
except Exception as e:
|
|
13
|
+
print(f"Error reading text {filepath}: {e}")
|
|
14
|
+
return ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_csv_file(filepath):
|
|
18
|
+
"""Converts tabular data to vector-ready narrative text."""
|
|
19
|
+
narratives = []
|
|
20
|
+
try:
|
|
21
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
22
|
+
reader = csv.DictReader(f)
|
|
23
|
+
for row in reader:
|
|
24
|
+
sentence = ". ".join(
|
|
25
|
+
[f"The {col} is {val}" for col, val in row.items()]
|
|
26
|
+
)
|
|
27
|
+
narratives.append(sentence)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"Error reading CSV {filepath}: {e}")
|
|
30
|
+
return "\n".join(narratives)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_csv_rows(filepath):
|
|
34
|
+
"""Return list of (sentence, meta) per CSV row."""
|
|
35
|
+
rows = []
|
|
36
|
+
try:
|
|
37
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
38
|
+
reader = csv.DictReader(f)
|
|
39
|
+
for idx, row in enumerate(reader, start=1):
|
|
40
|
+
sentence = ". ".join([f"The {col} is {val}" for col, val in row.items()])
|
|
41
|
+
rows.append((sentence, {"source": filepath, "type": "tabular", "row": idx}))
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Error reading CSV {filepath}: {e}")
|
|
44
|
+
return rows
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def load_pdf_pages(filepath):
|
|
48
|
+
"""Return list of (page_text, meta) per PDF page."""
|
|
49
|
+
pages = []
|
|
50
|
+
try:
|
|
51
|
+
reader = PdfReader(filepath)
|
|
52
|
+
for i, page in enumerate(reader.pages, start=1):
|
|
53
|
+
text = page.extract_text() or ""
|
|
54
|
+
if text.strip():
|
|
55
|
+
pages.append((text, {"source": filepath, "type": "pdf", "page": i}))
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(f"Error reading PDF {filepath}: {e}")
|
|
58
|
+
return pages
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def ingest_folder(folder_path):
|
|
62
|
+
"""Recursively finds and loads supported files in a folder."""
|
|
63
|
+
documents = []
|
|
64
|
+
|
|
65
|
+
for root, _, files in os.walk(folder_path):
|
|
66
|
+
for file in files:
|
|
67
|
+
filepath = os.path.join(root, file)
|
|
68
|
+
|
|
69
|
+
if file.endswith(".txt") or file.endswith(".md"):
|
|
70
|
+
content = load_text_file(filepath)
|
|
71
|
+
if content:
|
|
72
|
+
documents.append((content, {"source": filepath, "type": "text"}))
|
|
73
|
+
|
|
74
|
+
elif file.endswith(".csv"):
|
|
75
|
+
documents.extend(load_csv_rows(filepath))
|
|
76
|
+
|
|
77
|
+
elif file.endswith(".json"):
|
|
78
|
+
try:
|
|
79
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
80
|
+
content = json.dumps(json.load(f))
|
|
81
|
+
if content:
|
|
82
|
+
documents.append((content, {"source": filepath, "type": "json"}))
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Error reading JSON {filepath}: {e}")
|
|
85
|
+
|
|
86
|
+
elif file.endswith(".pdf"):
|
|
87
|
+
documents.extend(load_pdf_pages(filepath))
|
|
88
|
+
|
|
89
|
+
return documents
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import chromadb
|
|
3
|
+
from chromadb.utils import embedding_functions
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LocalMemory:
|
|
7
|
+
"""Thin wrapper around ChromaDB with local SentenceTransformer embeddings."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, path="./memloop_data"):
|
|
10
|
+
self.client = chromadb.PersistentClient(path=path)
|
|
11
|
+
self.ef = embedding_functions.SentenceTransformerEmbeddingFunction(
|
|
12
|
+
model_name="all-MiniLM-L6-v2"
|
|
13
|
+
)
|
|
14
|
+
self.collection = self.client.get_or_create_collection(
|
|
15
|
+
name="agent_memory",
|
|
16
|
+
embedding_function=self.ef,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def save(self, text, metadata=None):
|
|
20
|
+
"""Upsert a document (safe for duplicates)."""
|
|
21
|
+
meta = metadata or {}
|
|
22
|
+
unique_str = (
|
|
23
|
+
text
|
|
24
|
+
+ str(meta.get("source", ""))
|
|
25
|
+
+ str(meta.get("page", ""))
|
|
26
|
+
+ str(meta.get("chunk_index", ""))
|
|
27
|
+
)
|
|
28
|
+
doc_id = hashlib.md5(unique_str.encode()).hexdigest()
|
|
29
|
+
self.collection.upsert(
|
|
30
|
+
documents=[text],
|
|
31
|
+
metadatas=[meta],
|
|
32
|
+
ids=[doc_id],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def search_with_meta(self, query, n_results=3):
|
|
36
|
+
"""Return (documents, metadatas) for top-n results."""
|
|
37
|
+
if self.collection.count() == 0:
|
|
38
|
+
return [], []
|
|
39
|
+
actual_n = min(n_results, self.collection.count())
|
|
40
|
+
results = self.collection.query(query_texts=[query], n_results=actual_n)
|
|
41
|
+
return results["documents"][0], results["metadatas"][0]
|
|
42
|
+
|
|
43
|
+
def count(self):
|
|
44
|
+
"""Number of documents stored."""
|
|
45
|
+
return self.collection.count()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from bs4 import BeautifulSoup
|
|
3
|
+
|
|
4
|
+
def crawl_and_extract(url, chunk_size=500, overlap=50):
|
|
5
|
+
"""Fetch a URL, strip boilerplate, return overlapping text chunks."""
|
|
6
|
+
|
|
7
|
+
# Define headers to mimic a real browser
|
|
8
|
+
headers = {
|
|
9
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Pass headers here to fix the 403 error
|
|
14
|
+
response = requests.get(url, headers=headers, timeout=15)
|
|
15
|
+
response.raise_for_status()
|
|
16
|
+
|
|
17
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
|
18
|
+
|
|
19
|
+
for tag in soup(["script", "style", "nav", "footer", "header"]):
|
|
20
|
+
tag.extract()
|
|
21
|
+
|
|
22
|
+
text = soup.get_text(separator=" ")
|
|
23
|
+
text = " ".join(text.split()) # collapse whitespace
|
|
24
|
+
|
|
25
|
+
if not text:
|
|
26
|
+
return []
|
|
27
|
+
|
|
28
|
+
step = max(chunk_size - overlap, 1)
|
|
29
|
+
chunks = [text[i : i + chunk_size] for i in range(0, len(text), step)]
|
|
30
|
+
return [c for c in chunks if c.strip()]
|
|
31
|
+
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(f"Error reading {url}: {e}")
|
|
34
|
+
return []
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memloop
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A local-first, dual-memory engine for AI Agents.
|
|
5
|
+
Home-page: https://github.com/vanshcodeworks/memloop
|
|
6
|
+
Author: Vansh
|
|
7
|
+
Author-email: vanshgoyal9528@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: chromadb
|
|
15
|
+
Requires-Dist: sentence-transformers
|
|
16
|
+
Requires-Dist: beautifulsoup4
|
|
17
|
+
Requires-Dist: requests
|
|
18
|
+
Requires-Dist: pypdf
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# MemLoop: Local Vector Memory for AI Agents
|
|
30
|
+
|
|
31
|
+
> **"Give your AI infinite memory without the API bills."**
|
|
32
|
+
|
|
33
|
+
**MemLoop** is a production-ready, local-first memory orchestration engine designed to give LLMs (like Gemini, GPT-4, Llama 3) long-term retention capabilities. It bridges the gap between transient context windows and persistent vector storage.
|
|
34
|
+
|
|
35
|
+
Unlike wrapper libraries, MemLoop implements a custom **Dual-Memory Architecture** (Short-term buffer + Long-term Vector Store) and runs entirely offline.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Why MemLoop?
|
|
40
|
+
|
|
41
|
+
* **Privacy-First & Offline:** Runs 100% locally using `ChromaDB` and `SentenceTransformers`. No OpenAI API keys required. Your data never leaves your machine.
|
|
42
|
+
* **Zero-Latency Caching:** Implements an **O(1) Semantic Cache** that intercepts repeated queries before they hit the Vector DB, reducing retrieval latency by ~99%.
|
|
43
|
+
* **Citation-Aware Retrieval:** Don't just get text; get the source. MemLoop tracks **Page Numbers, Row Indices, and URLs** so your AI can cite its sources (e.g., *"Source: manual.pdf, Page 12"*).
|
|
44
|
+
* **Universal Ingestion:** Built-in ETL pipeline that automatically ingests:
|
|
45
|
+
* **Websites** (Recursive crawling with `BeautifulSoup`)
|
|
46
|
+
* **PDFs & Docs** (Automatic text chunking)
|
|
47
|
+
* **Tabular Data** (CSV/Excel linearizer for vector compatibility)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Architecture
|
|
54
|
+
|
|
55
|
+
MemLoop decouples ingestion from retrieval, using a hybrid cache-first strategy to ensure speed and accuracy.
|
|
56
|
+
|
|
57
|
+
```mermaid
|
|
58
|
+
graph TD
|
|
59
|
+
subgraph MemLoop_Engine [MemLoop Core]
|
|
60
|
+
Query[User Query] --> Cache{Check Cache?}
|
|
61
|
+
Cache -- Hit (0.01ms) --> Response
|
|
62
|
+
Cache -- Miss --> VectorDB[(ChromaDB <br/> Local Store)]
|
|
63
|
+
VectorDB --> Rerank[Context Reranker]
|
|
64
|
+
Rerank --> Response
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subgraph Ingestion_Layer [ETL Pipeline]
|
|
68
|
+
Web[Web Scraper] --> Chunker
|
|
69
|
+
Files[PDF/CSV Loader] --> Chunker
|
|
70
|
+
Chunker --> Embed[Local Embeddings]
|
|
71
|
+
Embed --> VectorDB
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
### 1. Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install memloop
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. The Interactive CLI (Chat with your Data)
|
|
88
|
+
|
|
89
|
+
Launch the built-in terminal interface to test your memory engine instantly.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
$ memloop
|
|
93
|
+
|
|
94
|
+
[SYSTEM]: Initializing Neural Link...
|
|
95
|
+
[USER]: /learn https://en.wikipedia.org/wiki/Artificial_intelligence
|
|
96
|
+
[SYSTEM]: Success. Absorbed 45 chunks.
|
|
97
|
+
[USER]: What is AI?
|
|
98
|
+
[MEMLOOP]: "AI is intelligence demonstrated by machines..." (Source: Wikipedia, Chunk 12)
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Python SDK (Build your own Agent)
|
|
103
|
+
|
|
104
|
+
Integrate MemLoop into your Python projects in 3 lines of code.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from memloop import MemLoop
|
|
108
|
+
|
|
109
|
+
# Initialize Brain (Persists to ./memloop_data)
|
|
110
|
+
brain = MemLoop()
|
|
111
|
+
|
|
112
|
+
# A. Ingest Knowledge
|
|
113
|
+
print("Ingesting documentation...")
|
|
114
|
+
brain.learn_url("https://docs.python.org/3/")
|
|
115
|
+
brain.learn_local("./my_documents_folder")
|
|
116
|
+
|
|
117
|
+
# B. Add Conversation Context
|
|
118
|
+
brain.add_memory("User is building a React app.")
|
|
119
|
+
|
|
120
|
+
# C. Retrieve Context (with Caching & Citations)
|
|
121
|
+
context = brain.recall("How do python decorators work?")
|
|
122
|
+
|
|
123
|
+
print(context)
|
|
124
|
+
# Output:
|
|
125
|
+
# "Short Term: User is building a React app..."
|
|
126
|
+
# "Long Term: [1] Decorators are functions... (Ref: python.org, Section 4.2)"
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Integration Example: MemLoop + Gemini
|
|
133
|
+
|
|
134
|
+
Here is how to use MemLoop as the "Long-Term Memory" for a Gemini (or OpenAI) agent.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import google.generativeai as genai
|
|
138
|
+
from memloop import MemLoop
|
|
139
|
+
|
|
140
|
+
# 1. Setup Memory
|
|
141
|
+
brain = MemLoop()
|
|
142
|
+
|
|
143
|
+
# 2. Setup LLM
|
|
144
|
+
genai.configure(api_key="YOUR_API_KEY")
|
|
145
|
+
model = genai.GenerativeModel('gemini-2.5-flash')
|
|
146
|
+
|
|
147
|
+
def ask_agent(query):
|
|
148
|
+
# Retrieve relevant memories locally (Free & Fast)
|
|
149
|
+
context = brain.recall(query)
|
|
150
|
+
|
|
151
|
+
# Send only relevant context to LLM
|
|
152
|
+
prompt = f"""
|
|
153
|
+
Use the following context to answer the user.
|
|
154
|
+
Context: {context}
|
|
155
|
+
|
|
156
|
+
User: {query}
|
|
157
|
+
Answer:
|
|
158
|
+
"""
|
|
159
|
+
response = model.generate_content(prompt)
|
|
160
|
+
return response.text
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Supported Formats
|
|
167
|
+
|
|
168
|
+
| Format | Features |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| **.txt / .md** | Standard text chunking with overlap. |
|
|
171
|
+
| **.csv** | **Row Linearization**: Converts rows into narrative sentences for better vector matching. |
|
|
172
|
+
| **.pdf** | **Page Tracking**: extracts text while preserving page numbers for citations. |
|
|
173
|
+
| **URLs** | **Smart Scraper**: Auto-removes HTML boilerplate (scripts, navbars, ads). |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 🗺️ Roadmap
|
|
178
|
+
|
|
179
|
+
* [x] Local Vector Storage (ChromaDB)
|
|
180
|
+
* [x] Semantic Caching (LRU Strategy)
|
|
181
|
+
* [x] Web & Local File Ingestion
|
|
182
|
+
* [ ] Multi-Modal Support (Image Embeddings)
|
|
183
|
+
* [ ] GraphRAG Integration (Knowledge Graphs)
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Contributing
|
|
188
|
+
|
|
189
|
+
Contributions are welcome! Please open an issue or submit a PR.
|
|
190
|
+
|
|
191
|
+
1. Fork the repo
|
|
192
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
193
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
194
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
195
|
+
5. Open a Pull Request
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
**Built with ❤️ by [Vansh**](https://github.com/vanshcodeworks)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
memloop/__init__.py
|
|
4
|
+
memloop/brain.py
|
|
5
|
+
memloop/cli.py
|
|
6
|
+
memloop/file_loader.py
|
|
7
|
+
memloop/storage.py
|
|
8
|
+
memloop/web_reader.py
|
|
9
|
+
memloop.egg-info/PKG-INFO
|
|
10
|
+
memloop.egg-info/SOURCES.txt
|
|
11
|
+
memloop.egg-info/dependency_links.txt
|
|
12
|
+
memloop.egg-info/entry_points.txt
|
|
13
|
+
memloop.egg-info/requires.txt
|
|
14
|
+
memloop.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
memloop
|
memloop-0.1.0/setup.cfg
ADDED
memloop-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
this_directory = os.path.abspath(os.path.dirname(__file__))
|
|
5
|
+
with open(os.path.join(this_directory, "README.md"), encoding="utf-8") as f:
|
|
6
|
+
long_description = f.read()
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="memloop",
|
|
10
|
+
version="0.1.0",
|
|
11
|
+
packages=find_packages(),
|
|
12
|
+
install_requires=[
|
|
13
|
+
"chromadb",
|
|
14
|
+
"sentence-transformers",
|
|
15
|
+
"beautifulsoup4",
|
|
16
|
+
"requests",
|
|
17
|
+
"pypdf",
|
|
18
|
+
],
|
|
19
|
+
entry_points={
|
|
20
|
+
"console_scripts": [
|
|
21
|
+
"memloop=memloop.cli:main",
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
author="Vansh",
|
|
25
|
+
author_email="vanshgoyal9528@gmail.com",
|
|
26
|
+
description="A local-first, dual-memory engine for AI Agents.",
|
|
27
|
+
long_description=long_description,
|
|
28
|
+
long_description_content_type="text/markdown",
|
|
29
|
+
url="https://github.com/vanshcodeworks/memloop",
|
|
30
|
+
classifiers=[
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"License :: OSI Approved :: MIT License",
|
|
33
|
+
"Operating System :: OS Independent",
|
|
34
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
35
|
+
],
|
|
36
|
+
python_requires=">=3.8",
|
|
37
|
+
)
|