streetai-memory 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.
- streetai_memory-0.1.0/LICENSE +19 -0
- streetai_memory-0.1.0/PKG-INFO +289 -0
- streetai_memory-0.1.0/README.md +228 -0
- streetai_memory-0.1.0/pyproject.toml +68 -0
- streetai_memory-0.1.0/setup.cfg +4 -0
- streetai_memory-0.1.0/streetai/__init__.py +63 -0
- streetai_memory-0.1.0/streetai/adapters/__init__.py +16 -0
- streetai_memory-0.1.0/streetai/adapters/anthropic.py +147 -0
- streetai_memory-0.1.0/streetai/adapters/gemini.py +185 -0
- streetai_memory-0.1.0/streetai/adapters/openai.py +152 -0
- streetai_memory-0.1.0/streetai/chunking.py +115 -0
- streetai_memory-0.1.0/streetai/config.py +70 -0
- streetai_memory-0.1.0/streetai/decay.py +35 -0
- streetai_memory-0.1.0/streetai/encoder.py +45 -0
- streetai_memory-0.1.0/streetai/memory.py +369 -0
- streetai_memory-0.1.0/streetai/prompt.py +25 -0
- streetai_memory-0.1.0/streetai/py.typed +0 -0
- streetai_memory-0.1.0/streetai/signal.py +87 -0
- streetai_memory-0.1.0/streetai/stack.py +55 -0
- streetai_memory-0.1.0/streetai/store.py +130 -0
- streetai_memory-0.1.0/streetai_memory.egg-info/PKG-INFO +289 -0
- streetai_memory-0.1.0/streetai_memory.egg-info/SOURCES.txt +27 -0
- streetai_memory-0.1.0/streetai_memory.egg-info/dependency_links.txt +1 -0
- streetai_memory-0.1.0/streetai_memory.egg-info/requires.txt +24 -0
- streetai_memory-0.1.0/streetai_memory.egg-info/top_level.txt +1 -0
- streetai_memory-0.1.0/tests/test_adapters.py +246 -0
- streetai_memory-0.1.0/tests/test_chunking.py +81 -0
- streetai_memory-0.1.0/tests/test_decay.py +66 -0
- streetai_memory-0.1.0/tests/test_memory.py +162 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
|
|
17
|
+
Copyright 2026 Street AI
|
|
18
|
+
|
|
19
|
+
Full license text: https://www.apache.org/licenses/LICENSE-2.0.txt
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streetai-memory
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Continuously learning memory layer for LLM applications: signals, stacks, decay, two-tier retrieval.
|
|
5
|
+
Author: Tem-Degu
|
|
6
|
+
License: Apache License
|
|
7
|
+
Version 2.0, January 2004
|
|
8
|
+
http://www.apache.org/licenses/
|
|
9
|
+
|
|
10
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
11
|
+
you may not use this file except in compliance with the License.
|
|
12
|
+
You may obtain a copy of the License at
|
|
13
|
+
|
|
14
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
15
|
+
|
|
16
|
+
Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
See the License for the specific language governing permissions and
|
|
20
|
+
limitations under the License.
|
|
21
|
+
|
|
22
|
+
Copyright 2026 Street AI
|
|
23
|
+
|
|
24
|
+
Full license text: https://www.apache.org/licenses/LICENSE-2.0.txt
|
|
25
|
+
|
|
26
|
+
Project-URL: Homepage, https://github.com/Tem-Degu/streetai-memory
|
|
27
|
+
Project-URL: Repository, https://github.com/Tem-Degu/streetai-memory
|
|
28
|
+
Project-URL: Issues, https://github.com/Tem-Degu/streetai-memory/issues
|
|
29
|
+
Keywords: llm,memory,ai,rag,embeddings,chatbot
|
|
30
|
+
Classifier: Development Status :: 3 - Alpha
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
37
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
38
|
+
Requires-Python: >=3.10
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: numpy<3,>=1.24
|
|
42
|
+
Requires-Dist: faiss-cpu>=1.7.4
|
|
43
|
+
Requires-Dist: fastembed>=0.3.0
|
|
44
|
+
Provides-Extra: anthropic
|
|
45
|
+
Requires-Dist: anthropic>=0.40.0; extra == "anthropic"
|
|
46
|
+
Provides-Extra: openai
|
|
47
|
+
Requires-Dist: openai>=1.50.0; extra == "openai"
|
|
48
|
+
Provides-Extra: gemini
|
|
49
|
+
Requires-Dist: google-genai>=0.3.0; extra == "gemini"
|
|
50
|
+
Provides-Extra: all
|
|
51
|
+
Requires-Dist: anthropic>=0.40.0; extra == "all"
|
|
52
|
+
Requires-Dist: openai>=1.50.0; extra == "all"
|
|
53
|
+
Requires-Dist: google-genai>=0.3.0; extra == "all"
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
57
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
58
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
59
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
# Street AI
|
|
63
|
+
|
|
64
|
+
> Continuously learning memory layer for LLM applications.
|
|
65
|
+
> Your AI's memory grows forever. Your token bill doesn't.
|
|
66
|
+
|
|
67
|
+
Street AI sits between your application and the LLM API. It stores conversation as
|
|
68
|
+
**signals** organized into **stacks**, decays old data automatically, and retrieves
|
|
69
|
+
only what's relevant on each turn — so you send a tiny prompt instead of the full
|
|
70
|
+
conversation history.
|
|
71
|
+
|
|
72
|
+
## Status
|
|
73
|
+
|
|
74
|
+
Alpha (`0.1.0`). API will change. Pin a version if you depend on it.
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install streetai-memory
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The PyPI name is `streetai-memory`; the import path is `streetai`:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from streetai import Memory, MemoryRegistry, Config
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
First use downloads a ~25MB embedding model (`all-MiniLM-L6-v2`) into a local cache.
|
|
89
|
+
|
|
90
|
+
To install with provider adapters:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install "streetai-memory[anthropic]" # Anthropic
|
|
94
|
+
pip install "streetai-memory[openai]" # OpenAI (also DeepSeek, Together, Groq)
|
|
95
|
+
pip install "streetai-memory[gemini]" # Google Gemini
|
|
96
|
+
pip install "streetai-memory[all]" # all of the above
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Quickstart
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from streetai import MemoryRegistry
|
|
103
|
+
|
|
104
|
+
registry = MemoryRegistry("./memory.db")
|
|
105
|
+
mem = registry.get("user_123")
|
|
106
|
+
|
|
107
|
+
mem.add_message("Hi, I'm planning a trip to Japan.", role="user")
|
|
108
|
+
mem.add_message("Great! Which cities?", role="assistant")
|
|
109
|
+
|
|
110
|
+
prompt = mem.build_prompt("What did I say about Japan?")
|
|
111
|
+
|
|
112
|
+
# prompt.messages -> list of {role, content} ready for any LLM API
|
|
113
|
+
# prompt.retrieved -> signals that were pulled in (pass to post_process)
|
|
114
|
+
# prompt.inspector -> debug info (stacks activated, scores, etc.)
|
|
115
|
+
|
|
116
|
+
# After your LLM responds:
|
|
117
|
+
# response_text = your_llm(messages=prompt.messages)
|
|
118
|
+
# mem.post_process(prompt.retrieved, response_text)
|
|
119
|
+
# mem.add_message("What did I say about Japan?", role="user")
|
|
120
|
+
# mem.add_message(response_text, role="assistant")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For a fully runnable version, see [`examples/quickstart.py`](./examples/quickstart.py).
|
|
124
|
+
|
|
125
|
+
## Drop-in adapters
|
|
126
|
+
|
|
127
|
+
The adapters wrap a real provider client. You use the same SDK API you already know;
|
|
128
|
+
memory is read and written transparently on every call.
|
|
129
|
+
|
|
130
|
+
### Anthropic
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from anthropic import Anthropic
|
|
134
|
+
from streetai.adapters.anthropic import with_memory
|
|
135
|
+
|
|
136
|
+
client = with_memory(Anthropic(), memory_id="user_123")
|
|
137
|
+
|
|
138
|
+
response = client.messages.create(
|
|
139
|
+
model="claude-sonnet-4-6",
|
|
140
|
+
max_tokens=1024,
|
|
141
|
+
system="You are helpful.",
|
|
142
|
+
messages=[{"role": "user", "content": "What did I mention earlier?"}],
|
|
143
|
+
)
|
|
144
|
+
print(response.content[0].text)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Full example: [`examples/anthropic_chat.py`](./examples/anthropic_chat.py).
|
|
148
|
+
|
|
149
|
+
### OpenAI
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from openai import OpenAI
|
|
153
|
+
from streetai.adapters.openai import with_memory
|
|
154
|
+
|
|
155
|
+
client = with_memory(OpenAI(), memory_id="user_123")
|
|
156
|
+
|
|
157
|
+
response = client.chat.completions.create(
|
|
158
|
+
model="gpt-4o-mini",
|
|
159
|
+
messages=[{"role": "user", "content": "What did I mention earlier?"}],
|
|
160
|
+
)
|
|
161
|
+
print(response.choices[0].message.content)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Full example: [`examples/openai_chat.py`](./examples/openai_chat.py).
|
|
165
|
+
|
|
166
|
+
### DeepSeek (uses the OpenAI adapter)
|
|
167
|
+
|
|
168
|
+
DeepSeek is OpenAI-API-compatible. Use the OpenAI adapter with `base_url`:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import os
|
|
172
|
+
from openai import OpenAI
|
|
173
|
+
from streetai.adapters.openai import with_memory
|
|
174
|
+
|
|
175
|
+
deepseek = OpenAI(
|
|
176
|
+
api_key=os.environ["DEEPSEEK_API_KEY"],
|
|
177
|
+
base_url="https://api.deepseek.com/v1",
|
|
178
|
+
)
|
|
179
|
+
client = with_memory(deepseek, memory_id="user_123")
|
|
180
|
+
|
|
181
|
+
response = client.chat.completions.create(
|
|
182
|
+
model="deepseek-chat",
|
|
183
|
+
messages=[{"role": "user", "content": "What did I mention earlier?"}],
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The same pattern works for **Together**, **Anyscale**, **Groq**, and any other
|
|
188
|
+
OpenAI-compatible endpoint. Full example: [`examples/deepseek_chat.py`](./examples/deepseek_chat.py).
|
|
189
|
+
|
|
190
|
+
### Google Gemini
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from google import genai
|
|
194
|
+
from streetai.adapters.gemini import with_memory
|
|
195
|
+
|
|
196
|
+
client = with_memory(genai.Client(api_key="..."), memory_id="user_123")
|
|
197
|
+
|
|
198
|
+
response = client.models.generate_content(
|
|
199
|
+
model="gemini-2.0-flash",
|
|
200
|
+
contents="What did I mention earlier?",
|
|
201
|
+
)
|
|
202
|
+
print(response.text)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Full example: [`examples/gemini_chat.py`](./examples/gemini_chat.py).
|
|
206
|
+
|
|
207
|
+
## How it works
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
your message
|
|
211
|
+
|
|
|
212
|
+
v
|
|
213
|
+
[1] split into chunks (sentence-sized signals)
|
|
214
|
+
|
|
|
215
|
+
v
|
|
216
|
+
[2] embed each chunk to a 384-dim vector
|
|
217
|
+
|
|
|
218
|
+
v
|
|
219
|
+
[3] assign to a stack (cluster of related signals) by cosine similarity
|
|
220
|
+
|
|
|
221
|
+
v
|
|
222
|
+
[4] when a new query arrives:
|
|
223
|
+
- find top-K most relevant stacks (FAISS)
|
|
224
|
+
- within those stacks, surface signals that pass the activation threshold
|
|
225
|
+
- drop signals whose effective weight has decayed below death
|
|
226
|
+
|
|
|
227
|
+
v
|
|
228
|
+
[5] build a small prompt:
|
|
229
|
+
[retrieved context] + [last N messages verbatim] + [new query]
|
|
230
|
+
|
|
|
231
|
+
v
|
|
232
|
+
[6] after the LLM responds:
|
|
233
|
+
- boost signals that matched the response (they helped)
|
|
234
|
+
- demote signals that didn't (they were noise)
|
|
235
|
+
- decay continues until the signal is used again
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Signals refresh their age clock every time they're retrieved — frequently useful
|
|
239
|
+
data stays sharp; unused data fades. No retraining, no manual pruning.
|
|
240
|
+
|
|
241
|
+
## Compared to alternatives
|
|
242
|
+
|
|
243
|
+
| | Plain chat history | RAG (vector DB) | Street AI |
|
|
244
|
+
|---|---|---|---|
|
|
245
|
+
| Prompt grows with conversation | Yes — linear | No (replaces history) | No (compresses history) |
|
|
246
|
+
| Recent context kept verbatim | Yes | No — replaced by retrieval | Yes — recency window |
|
|
247
|
+
| Time-aware (decay) | No | No | Yes — built in |
|
|
248
|
+
| Learns from outcomes | No | No | Yes — boost/demote |
|
|
249
|
+
| Self-organizing | N/A | Manual chunking | Yes — auto-stacks |
|
|
250
|
+
| Cross-provider | Yes | Sometimes | Yes |
|
|
251
|
+
|
|
252
|
+
## Configuration
|
|
253
|
+
|
|
254
|
+
Override defaults with `Config`:
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from streetai import MemoryRegistry, Config
|
|
258
|
+
|
|
259
|
+
cfg = Config(
|
|
260
|
+
recency_turns=5, # last 5 messages verbatim (default 3)
|
|
261
|
+
decay_rate=1.0/86400, # 1-day half-life (default ~ 1 week)
|
|
262
|
+
stack_threshold=0.65, # tighter stack assignment (default 0.55)
|
|
263
|
+
activation_threshold=0.1, # min score for a signal to surface (default 0.15)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
registry = MemoryRegistry("./memory.db", config=cfg)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
All tunables: see [`streetai/config.py`](./streetai/config.py).
|
|
270
|
+
|
|
271
|
+
## Limitations (v0.1)
|
|
272
|
+
|
|
273
|
+
- **Sync clients only.** Async wrappers come later.
|
|
274
|
+
- **Non-streaming only.** `stream=True` raises `NotImplementedError`.
|
|
275
|
+
- **English-tuned defaults.** Chunking and thresholds may need tuning for other languages.
|
|
276
|
+
- **fastembed is required.** Pluggable encoders come in a future version.
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
git clone https://github.com/Tem-Degu/streetai-memory.git
|
|
282
|
+
cd streetai-memory
|
|
283
|
+
pip install -e ".[dev]"
|
|
284
|
+
pytest
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
Apache 2.0
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Street AI
|
|
2
|
+
|
|
3
|
+
> Continuously learning memory layer for LLM applications.
|
|
4
|
+
> Your AI's memory grows forever. Your token bill doesn't.
|
|
5
|
+
|
|
6
|
+
Street AI sits between your application and the LLM API. It stores conversation as
|
|
7
|
+
**signals** organized into **stacks**, decays old data automatically, and retrieves
|
|
8
|
+
only what's relevant on each turn — so you send a tiny prompt instead of the full
|
|
9
|
+
conversation history.
|
|
10
|
+
|
|
11
|
+
## Status
|
|
12
|
+
|
|
13
|
+
Alpha (`0.1.0`). API will change. Pin a version if you depend on it.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install streetai-memory
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The PyPI name is `streetai-memory`; the import path is `streetai`:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from streetai import Memory, MemoryRegistry, Config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
First use downloads a ~25MB embedding model (`all-MiniLM-L6-v2`) into a local cache.
|
|
28
|
+
|
|
29
|
+
To install with provider adapters:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install "streetai-memory[anthropic]" # Anthropic
|
|
33
|
+
pip install "streetai-memory[openai]" # OpenAI (also DeepSeek, Together, Groq)
|
|
34
|
+
pip install "streetai-memory[gemini]" # Google Gemini
|
|
35
|
+
pip install "streetai-memory[all]" # all of the above
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from streetai import MemoryRegistry
|
|
42
|
+
|
|
43
|
+
registry = MemoryRegistry("./memory.db")
|
|
44
|
+
mem = registry.get("user_123")
|
|
45
|
+
|
|
46
|
+
mem.add_message("Hi, I'm planning a trip to Japan.", role="user")
|
|
47
|
+
mem.add_message("Great! Which cities?", role="assistant")
|
|
48
|
+
|
|
49
|
+
prompt = mem.build_prompt("What did I say about Japan?")
|
|
50
|
+
|
|
51
|
+
# prompt.messages -> list of {role, content} ready for any LLM API
|
|
52
|
+
# prompt.retrieved -> signals that were pulled in (pass to post_process)
|
|
53
|
+
# prompt.inspector -> debug info (stacks activated, scores, etc.)
|
|
54
|
+
|
|
55
|
+
# After your LLM responds:
|
|
56
|
+
# response_text = your_llm(messages=prompt.messages)
|
|
57
|
+
# mem.post_process(prompt.retrieved, response_text)
|
|
58
|
+
# mem.add_message("What did I say about Japan?", role="user")
|
|
59
|
+
# mem.add_message(response_text, role="assistant")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For a fully runnable version, see [`examples/quickstart.py`](./examples/quickstart.py).
|
|
63
|
+
|
|
64
|
+
## Drop-in adapters
|
|
65
|
+
|
|
66
|
+
The adapters wrap a real provider client. You use the same SDK API you already know;
|
|
67
|
+
memory is read and written transparently on every call.
|
|
68
|
+
|
|
69
|
+
### Anthropic
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from anthropic import Anthropic
|
|
73
|
+
from streetai.adapters.anthropic import with_memory
|
|
74
|
+
|
|
75
|
+
client = with_memory(Anthropic(), memory_id="user_123")
|
|
76
|
+
|
|
77
|
+
response = client.messages.create(
|
|
78
|
+
model="claude-sonnet-4-6",
|
|
79
|
+
max_tokens=1024,
|
|
80
|
+
system="You are helpful.",
|
|
81
|
+
messages=[{"role": "user", "content": "What did I mention earlier?"}],
|
|
82
|
+
)
|
|
83
|
+
print(response.content[0].text)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Full example: [`examples/anthropic_chat.py`](./examples/anthropic_chat.py).
|
|
87
|
+
|
|
88
|
+
### OpenAI
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from openai import OpenAI
|
|
92
|
+
from streetai.adapters.openai import with_memory
|
|
93
|
+
|
|
94
|
+
client = with_memory(OpenAI(), memory_id="user_123")
|
|
95
|
+
|
|
96
|
+
response = client.chat.completions.create(
|
|
97
|
+
model="gpt-4o-mini",
|
|
98
|
+
messages=[{"role": "user", "content": "What did I mention earlier?"}],
|
|
99
|
+
)
|
|
100
|
+
print(response.choices[0].message.content)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Full example: [`examples/openai_chat.py`](./examples/openai_chat.py).
|
|
104
|
+
|
|
105
|
+
### DeepSeek (uses the OpenAI adapter)
|
|
106
|
+
|
|
107
|
+
DeepSeek is OpenAI-API-compatible. Use the OpenAI adapter with `base_url`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import os
|
|
111
|
+
from openai import OpenAI
|
|
112
|
+
from streetai.adapters.openai import with_memory
|
|
113
|
+
|
|
114
|
+
deepseek = OpenAI(
|
|
115
|
+
api_key=os.environ["DEEPSEEK_API_KEY"],
|
|
116
|
+
base_url="https://api.deepseek.com/v1",
|
|
117
|
+
)
|
|
118
|
+
client = with_memory(deepseek, memory_id="user_123")
|
|
119
|
+
|
|
120
|
+
response = client.chat.completions.create(
|
|
121
|
+
model="deepseek-chat",
|
|
122
|
+
messages=[{"role": "user", "content": "What did I mention earlier?"}],
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The same pattern works for **Together**, **Anyscale**, **Groq**, and any other
|
|
127
|
+
OpenAI-compatible endpoint. Full example: [`examples/deepseek_chat.py`](./examples/deepseek_chat.py).
|
|
128
|
+
|
|
129
|
+
### Google Gemini
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from google import genai
|
|
133
|
+
from streetai.adapters.gemini import with_memory
|
|
134
|
+
|
|
135
|
+
client = with_memory(genai.Client(api_key="..."), memory_id="user_123")
|
|
136
|
+
|
|
137
|
+
response = client.models.generate_content(
|
|
138
|
+
model="gemini-2.0-flash",
|
|
139
|
+
contents="What did I mention earlier?",
|
|
140
|
+
)
|
|
141
|
+
print(response.text)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Full example: [`examples/gemini_chat.py`](./examples/gemini_chat.py).
|
|
145
|
+
|
|
146
|
+
## How it works
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
your message
|
|
150
|
+
|
|
|
151
|
+
v
|
|
152
|
+
[1] split into chunks (sentence-sized signals)
|
|
153
|
+
|
|
|
154
|
+
v
|
|
155
|
+
[2] embed each chunk to a 384-dim vector
|
|
156
|
+
|
|
|
157
|
+
v
|
|
158
|
+
[3] assign to a stack (cluster of related signals) by cosine similarity
|
|
159
|
+
|
|
|
160
|
+
v
|
|
161
|
+
[4] when a new query arrives:
|
|
162
|
+
- find top-K most relevant stacks (FAISS)
|
|
163
|
+
- within those stacks, surface signals that pass the activation threshold
|
|
164
|
+
- drop signals whose effective weight has decayed below death
|
|
165
|
+
|
|
|
166
|
+
v
|
|
167
|
+
[5] build a small prompt:
|
|
168
|
+
[retrieved context] + [last N messages verbatim] + [new query]
|
|
169
|
+
|
|
|
170
|
+
v
|
|
171
|
+
[6] after the LLM responds:
|
|
172
|
+
- boost signals that matched the response (they helped)
|
|
173
|
+
- demote signals that didn't (they were noise)
|
|
174
|
+
- decay continues until the signal is used again
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Signals refresh their age clock every time they're retrieved — frequently useful
|
|
178
|
+
data stays sharp; unused data fades. No retraining, no manual pruning.
|
|
179
|
+
|
|
180
|
+
## Compared to alternatives
|
|
181
|
+
|
|
182
|
+
| | Plain chat history | RAG (vector DB) | Street AI |
|
|
183
|
+
|---|---|---|---|
|
|
184
|
+
| Prompt grows with conversation | Yes — linear | No (replaces history) | No (compresses history) |
|
|
185
|
+
| Recent context kept verbatim | Yes | No — replaced by retrieval | Yes — recency window |
|
|
186
|
+
| Time-aware (decay) | No | No | Yes — built in |
|
|
187
|
+
| Learns from outcomes | No | No | Yes — boost/demote |
|
|
188
|
+
| Self-organizing | N/A | Manual chunking | Yes — auto-stacks |
|
|
189
|
+
| Cross-provider | Yes | Sometimes | Yes |
|
|
190
|
+
|
|
191
|
+
## Configuration
|
|
192
|
+
|
|
193
|
+
Override defaults with `Config`:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from streetai import MemoryRegistry, Config
|
|
197
|
+
|
|
198
|
+
cfg = Config(
|
|
199
|
+
recency_turns=5, # last 5 messages verbatim (default 3)
|
|
200
|
+
decay_rate=1.0/86400, # 1-day half-life (default ~ 1 week)
|
|
201
|
+
stack_threshold=0.65, # tighter stack assignment (default 0.55)
|
|
202
|
+
activation_threshold=0.1, # min score for a signal to surface (default 0.15)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
registry = MemoryRegistry("./memory.db", config=cfg)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
All tunables: see [`streetai/config.py`](./streetai/config.py).
|
|
209
|
+
|
|
210
|
+
## Limitations (v0.1)
|
|
211
|
+
|
|
212
|
+
- **Sync clients only.** Async wrappers come later.
|
|
213
|
+
- **Non-streaming only.** `stream=True` raises `NotImplementedError`.
|
|
214
|
+
- **English-tuned defaults.** Chunking and thresholds may need tuning for other languages.
|
|
215
|
+
- **fastembed is required.** Pluggable encoders come in a future version.
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
git clone https://github.com/Tem-Degu/streetai-memory.git
|
|
221
|
+
cd streetai-memory
|
|
222
|
+
pip install -e ".[dev]"
|
|
223
|
+
pytest
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
Apache 2.0
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "streetai-memory"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Continuously learning memory layer for LLM applications: signals, stacks, decay, two-tier retrieval."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Tem-Degu" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["llm", "memory", "ai", "rag", "embeddings", "chatbot"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: Apache Software License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
dependencies = [
|
|
28
|
+
"numpy>=1.24,<3",
|
|
29
|
+
"faiss-cpu>=1.7.4",
|
|
30
|
+
"fastembed>=0.3.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
anthropic = ["anthropic>=0.40.0"]
|
|
35
|
+
openai = ["openai>=1.50.0"]
|
|
36
|
+
gemini = ["google-genai>=0.3.0"]
|
|
37
|
+
# DeepSeek uses the OpenAI-compatible API; install the `openai` extra.
|
|
38
|
+
all = [
|
|
39
|
+
"anthropic>=0.40.0",
|
|
40
|
+
"openai>=1.50.0",
|
|
41
|
+
"google-genai>=0.3.0",
|
|
42
|
+
]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=8.0",
|
|
45
|
+
"pytest-asyncio>=0.23",
|
|
46
|
+
"ruff>=0.5",
|
|
47
|
+
"build>=1.2",
|
|
48
|
+
"twine>=5.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[project.urls]
|
|
52
|
+
Homepage = "https://github.com/Tem-Degu/streetai-memory"
|
|
53
|
+
Repository = "https://github.com/Tem-Degu/streetai-memory"
|
|
54
|
+
Issues = "https://github.com/Tem-Degu/streetai-memory/issues"
|
|
55
|
+
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["."]
|
|
58
|
+
include = ["streetai*"]
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.package-data]
|
|
61
|
+
streetai = ["py.typed"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 100
|
|
65
|
+
target-version = "py310"
|
|
66
|
+
|
|
67
|
+
[tool.pytest.ini_options]
|
|
68
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Street AI: continuously learning memory layer for LLM applications.
|
|
2
|
+
|
|
3
|
+
Quickstart:
|
|
4
|
+
|
|
5
|
+
from streetai import Memory, MemoryRegistry, Config
|
|
6
|
+
|
|
7
|
+
registry = MemoryRegistry("./memory.db")
|
|
8
|
+
mem = registry.get("user_123")
|
|
9
|
+
|
|
10
|
+
mem.add_message("Hello", role="user")
|
|
11
|
+
prompt = mem.build_prompt("What did I say?")
|
|
12
|
+
# prompt.messages -> list[{role, content}] ready for any LLM API
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from .chunking import chunk_text
|
|
17
|
+
from .config import (
|
|
18
|
+
ACTIVATION_THRESHOLD,
|
|
19
|
+
BOOST_FACTOR,
|
|
20
|
+
Config,
|
|
21
|
+
DEATH_THRESHOLD,
|
|
22
|
+
DECAY_RATE,
|
|
23
|
+
DEFAULT_CONFIG,
|
|
24
|
+
DEMOTE_FACTOR,
|
|
25
|
+
EMBEDDING_DIM,
|
|
26
|
+
EMBEDDING_MODEL,
|
|
27
|
+
HIGH_RELEVANCE,
|
|
28
|
+
LOW_RELEVANCE,
|
|
29
|
+
MAX_BASE_WEIGHT,
|
|
30
|
+
MAX_THINKING_SPACE,
|
|
31
|
+
RECENCY_TURNS,
|
|
32
|
+
STACK_THRESHOLD,
|
|
33
|
+
TOP_STACKS,
|
|
34
|
+
)
|
|
35
|
+
from .decay import exponential_decay, half_life_to_decay_rate, time_to_death
|
|
36
|
+
from .encoder import encode # encoder() is available via streetai.encoder.encoder
|
|
37
|
+
from .memory import Memory, MemoryRegistry
|
|
38
|
+
from .prompt import Prompt
|
|
39
|
+
from .signal import Signal
|
|
40
|
+
from .stack import Stack
|
|
41
|
+
from .store import Store
|
|
42
|
+
|
|
43
|
+
__version__ = "0.1.0"
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
# Primary API
|
|
47
|
+
"Memory",
|
|
48
|
+
"MemoryRegistry",
|
|
49
|
+
"Prompt",
|
|
50
|
+
"Config",
|
|
51
|
+
# Lower-level types (for power users)
|
|
52
|
+
"Signal",
|
|
53
|
+
"Stack",
|
|
54
|
+
"Store",
|
|
55
|
+
"chunk_text",
|
|
56
|
+
"encode",
|
|
57
|
+
"DEFAULT_CONFIG",
|
|
58
|
+
# Decay math (utility)
|
|
59
|
+
"exponential_decay",
|
|
60
|
+
"half_life_to_decay_rate",
|
|
61
|
+
"time_to_death",
|
|
62
|
+
"__version__",
|
|
63
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Drop-in memory wrappers for popular LLM provider SDKs.
|
|
2
|
+
|
|
3
|
+
Each adapter exposes ``with_memory(client, memory_id)`` returning a thin proxy
|
|
4
|
+
that mimics the real client's API. The proxy intercepts chat-completion calls,
|
|
5
|
+
injects retrieved memory into the prompt, calls the underlying client, and
|
|
6
|
+
writes the new turn back to memory.
|
|
7
|
+
|
|
8
|
+
Adapters are intentionally lightweight (~100 lines each). For custom providers
|
|
9
|
+
or advanced needs, use ``Memory.build_prompt()`` and ``Memory.post_process()``
|
|
10
|
+
directly.
|
|
11
|
+
|
|
12
|
+
Available adapters:
|
|
13
|
+
- ``streetai.adapters.anthropic`` — wraps ``anthropic.Anthropic``
|
|
14
|
+
- ``streetai.adapters.openai`` — wraps ``openai.OpenAI`` (also DeepSeek, Together, etc.)
|
|
15
|
+
- ``streetai.adapters.gemini`` — wraps ``google.genai.Client`` (added in Day 4)
|
|
16
|
+
"""
|