beanis 0.3.0__tar.gz → 0.3.2__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.
Files changed (43) hide show
  1. beanis-0.3.2/PKG-INFO +296 -0
  2. beanis-0.3.2/README.md +249 -0
  3. {beanis-0.3.0 → beanis-0.3.2}/beanis/__init__.py +1 -1
  4. beanis-0.3.2/beanis/cli.py +347 -0
  5. {beanis-0.3.0 → beanis-0.3.2}/beanis/mcp_server.py +3 -15
  6. {beanis-0.3.0 → beanis-0.3.2}/beanis/memory.py +82 -7
  7. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/indexes.py +2 -0
  8. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/init.py +10 -3
  9. {beanis-0.3.0 → beanis-0.3.2}/pyproject.toml +3 -2
  10. beanis-0.3.0/PKG-INFO +0 -719
  11. beanis-0.3.0/README.md +0 -672
  12. {beanis-0.3.0 → beanis-0.3.2}/LICENSE +0 -0
  13. {beanis-0.3.0 → beanis-0.3.2}/beanis/exceptions.py +0 -0
  14. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/__init__.py +0 -0
  15. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/actions.py +0 -0
  16. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/custom_encoders.py +0 -0
  17. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/custom_types/__init__.py +0 -0
  18. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/custom_types/decimal.py +0 -0
  19. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/documents.py +0 -0
  20. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/fields.py +0 -0
  21. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/interfaces/__init__.py +0 -0
  22. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/interfaces/clone.py +0 -0
  23. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/interfaces/detector.py +0 -0
  24. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/interfaces/getters.py +0 -0
  25. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/interfaces/inheritance.py +0 -0
  26. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/interfaces/setters.py +0 -0
  27. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/registry.py +0 -0
  28. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/settings/__init__.py +0 -0
  29. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/settings/base.py +0 -0
  30. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/settings/document.py +0 -0
  31. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/__init__.py +0 -0
  32. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/dump.py +0 -0
  33. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/encoder.py +0 -0
  34. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/general.py +0 -0
  35. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/parsing.py +0 -0
  36. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/projection.py +0 -0
  37. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/pydantic.py +0 -0
  38. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/self_validation.py +0 -0
  39. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/state.py +0 -0
  40. {beanis-0.3.0 → beanis-0.3.2}/beanis/odm/utils/typing.py +0 -0
  41. {beanis-0.3.0 → beanis-0.3.2}/beanis/py.typed +0 -0
  42. {beanis-0.3.0 → beanis-0.3.2}/beanis/pydantic_check.py +0 -0
  43. {beanis-0.3.0 → beanis-0.3.2}/beanis/test.py +0 -0
beanis-0.3.2/PKG-INFO ADDED
@@ -0,0 +1,296 @@
1
+ Metadata-Version: 2.4
2
+ Name: beanis
3
+ Version: 0.3.2
4
+ Summary: Asynchronous Python ODM for Redis
5
+ Keywords: redis,odm,orm,pydantic,async,python
6
+ Author-email: Andrei Stefan Bejgu <stefan.bejgu@gmail.com>
7
+ Requires-Python: >=3.9,<4.0
8
+ Description-Content-Type: text/markdown
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Database
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Typing :: Typed
16
+ License-File: LICENSE
17
+ Requires-Dist: redis[hiredis]>=5.0.1
18
+ Requires-Dist: pydantic>=1.10,<3.0
19
+ Requires-Dist: lazy-model==0.2.0
20
+ Requires-Dist: msgspec>=0.18.0
21
+ Requires-Dist: typing-extensions>=4.7; python_version < '3.11'
22
+ Requires-Dist: Pygments>=2.8.0 ; extra == "doc"
23
+ Requires-Dist: Markdown>=3.3 ; extra == "doc"
24
+ Requires-Dist: pydoc-markdown>=4.8 ; extra == "doc"
25
+ Requires-Dist: mkdocs>=1.4 ; extra == "doc"
26
+ Requires-Dist: mkdocs-material>=9.0 ; extra == "doc"
27
+ Requires-Dist: jinja2>=3.0.3 ; extra == "doc"
28
+ Requires-Dist: mcp>=2.3 ; extra == "mcp" and ( python_version >= '3.10')
29
+ Requires-Dist: fastembed>=0.9 ; extra == "mcp" and ( python_version >= '3.10')
30
+ Requires-Dist: pytest>=6.0.0 ; extra == "test"
31
+ Requires-Dist: pytest-asyncio>=0.21.0 ; extra == "test"
32
+ Requires-Dist: pytest-cov>=2.8.1 ; extra == "test"
33
+ Requires-Dist: fakeredis[lua]>=2.20.0 ; extra == "test"
34
+ Requires-Dist: flake8>=3 ; extra == "test"
35
+ Requires-Dist: pyright>=0 ; extra == "test"
36
+ Requires-Dist: httpx>=0.23.0 ; extra == "test"
37
+ Requires-Dist: fastapi>=0.100 ; extra == "test"
38
+ Requires-Dist: pydantic-settings>=2 ; extra == "test"
39
+ Requires-Dist: pydantic-extra-types>=2 ; extra == "test"
40
+ Requires-Dist: pydantic[email] ; extra == "test"
41
+ Project-URL: homepage, https://github.com/andreim14/beanis
42
+ Project-URL: repository, https://github.com/andreim14/beanis
43
+ Provides-Extra: doc
44
+ Provides-Extra: mcp
45
+ Provides-Extra: test
46
+
47
+ <p align="center">
48
+ <picture>
49
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/andreim14/beanis/main/assets/logo/beanis-dark.svg">
50
+ <img src="https://raw.githubusercontent.com/andreim14/beanis/main/assets/logo/beanis.svg" alt="beanis" width="420">
51
+ </picture>
52
+ </p>
53
+
54
+ <div align="center">
55
+ <a href="https://pypi.python.org/pypi/beanis"><img src="https://img.shields.io/pypi/v/beanis?color=blue" alt="PyPI version"></a>
56
+ <a href="https://pypi.python.org/pypi/beanis"><img src="https://img.shields.io/pypi/pyversions/beanis?color=blue" alt="Python versions"></a>
57
+ <a href="https://github.com/andreim14/beanis/blob/main/LICENSE"><img src="https://img.shields.io/github/license/andreim14/beanis?color=blue" alt="License"></a>
58
+ </div>
59
+
60
+ Beanis is an async object-document mapper for Redis, built on Pydantic. You define a model, and Beanis stores each instance as a Redis hash, keeps secondary indexes up to date, and gives you typed objects back.
61
+
62
+ It also does vector search on the vector sets that ship with Redis 8, and it includes long-term memory for AI agents, usable from Python, from the `beanis` command line, or as an MCP server for Claude Code and other agents.
63
+
64
+ It works on plain Redis. No RedisJSON, RediSearch or Redis Stack.
65
+
66
+ [Documentation](https://andreim14.github.io/beanis/) · [PyPI](https://pypi.org/project/beanis/) · [Changelog](docs/changelog.md)
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ pip install beanis # the ODM
72
+ pip install "beanis[mcp]" # plus agent memory: CLI embeddings and MCP server (Python 3.10+)
73
+ ```
74
+
75
+ You need a Redis server. Redis 5+ is enough for documents and indexes; vector search and memory need Redis 8.0+:
76
+
77
+ ```bash
78
+ docker run -d -p 6379:6379 redis:8 # or: brew install redis
79
+ ```
80
+
81
+ ## Quick start
82
+
83
+ ```python
84
+ import asyncio
85
+
86
+ from pydantic import BaseModel
87
+ from redis.asyncio import Redis
88
+
89
+ from beanis import Document, Indexed, init_beanis
90
+
91
+
92
+ class Category(BaseModel):
93
+ name: str
94
+ description: str
95
+
96
+
97
+ class Product(Document):
98
+ name: str
99
+ price: Indexed(float) # sorted-set index: range queries
100
+ category: Indexed(str) # set index: exact match
101
+ tags: list[str] = []
102
+ stock: int = 0
103
+
104
+ class Settings:
105
+ key_prefix = "Product"
106
+
107
+
108
+ async def main():
109
+ redis = Redis(decode_responses=True)
110
+ await init_beanis(database=redis, document_models=[Product])
111
+
112
+ bar = Product(name="Dark chocolate", price=5.95, category="chocolate", stock=100)
113
+ await bar.insert()
114
+
115
+ same = await Product.get(bar.id)
116
+ cheap = await Product.find(price__lt=10)
117
+ chocolate = await Product.find(category="chocolate", price__gte=5)
118
+
119
+ await bar.update(price=6.50)
120
+ await bar.increment_field("stock", -1)
121
+ await bar.delete_self()
122
+
123
+
124
+ asyncio.run(main())
125
+ ```
126
+
127
+ `decode_responses=True` is required. Each document is the hash `Product:<id>`; nested models, lists, dicts, `datetime`, `Decimal`, `UUID` and enums are encoded for you and decoded on read.
128
+
129
+ ## Queries
130
+
131
+ `find()` takes keyword filters on indexed fields. Several filters are combined with AND.
132
+
133
+ ```python
134
+ await Product.find(category="chocolate") # exact match
135
+ await Product.find(price__gte=10, price__lte=50) # range: __gte, __lte, __gt, __lt
136
+ await Product.find(price=12.0) # exact match on a number
137
+ await Product.all(skip=0, limit=20) # every document, by insertion time
138
+ await Product.count()
139
+ ```
140
+
141
+ Filtering on a field that is not indexed raises `ValueError`. Sorting by other fields and full-text search are not built in; sort the returned list in Python, or use Redis's query engine directly if you need them.
142
+
143
+ Declare indexes with `Indexed(type)` or `Annotated[type, IndexedField()]`. Numbers get a sorted set, strings and other scalars get a set, `GeoPoint` gets a geo index (see the [geo tutorial](docs/tutorial/geo-spatial.md)).
144
+
145
+ ## Vector search
146
+
147
+ Embeddings live in a Redis 8 vector set (`VADD` / `VSIM`), next to the document's short fields so you can filter a search:
148
+
149
+ ```python
150
+ from typing import List
151
+
152
+ from typing_extensions import Annotated
153
+
154
+ from beanis import Document, IndexedField, VectorField
155
+
156
+
157
+ class Chunk(Document):
158
+ text: str
159
+ source: Annotated[str, IndexedField()]
160
+ embedding: Annotated[List[float], VectorField(dimensions=384)]
161
+
162
+
163
+ await Chunk(text="Refunds take 5 days", source="faq", embedding=embed("Refunds take 5 days")).insert()
164
+
165
+ for chunk, score in await Chunk.vector_search(embed("how long is a refund?"), k=3, filter='.source == "faq"'):
166
+ print(round(score, 3), chunk.text) # score: cosine similarity in [0, 1]
167
+ ```
168
+
169
+ `embed` is any function from text to a list of floats. Vectors use HNSW with int8 quantization by default (`VectorField(quantization="NOQUANT")` keeps float32). Updating or deleting a document updates its vector. `init_beanis` raises at startup if a model has vector fields and the server has no vector sets (Redis 7, Valkey).
170
+
171
+ ## Memory for agents
172
+
173
+ `Memory` stores what an agent should keep between conversations and finds it again by meaning. Memories are scoped by namespace, user, session and kind; a new memory that is nearly identical to an old one replaces it instead of piling up.
174
+
175
+ ```python
176
+ from beanis import Memory
177
+ from beanis.memory import fastembed_embedder
178
+
179
+ memory = Memory(redis, embed=fastembed_embedder(), namespace="support-bot")
180
+
181
+ await memory.remember("Prefers answers in Italian", user_id="u42", kind="preference")
182
+ for record, score in await memory.recall("which language should I answer in?", user_id="u42"):
183
+ print(round(score, 3), record.text)
184
+ await memory.recent(user_id="u42")
185
+ await memory.forget(record.id)
186
+ ```
187
+
188
+ `fastembed_embedder()` runs `BAAI/bge-small-en-v1.5` locally on the CPU (no API key). Any embedding function works, sync or async; use the same one for every writer of a namespace.
189
+
190
+ ### From the command line
191
+
192
+ `pip install "beanis[mcp]"` installs a `beanis` command. Every command prints JSON, so shell scripts and coding agents can use it directly.
193
+
194
+ ```bash
195
+ beanis doctor # checks Redis, vector sets, extras, stored memory
196
+ beanis memory remember "Deploys go out on Tuesdays" --kind decision
197
+ beanis memory recall "when do we deploy?" -k 3
198
+ beanis memory recent --limit 5
199
+ beanis memory forget <id>
200
+ beanis context # newest memories as plain text, for session hooks
201
+ ```
202
+
203
+ Configuration is by environment: `REDIS_URL` (default `redis://localhost:6379/0`, or pass `--url`), `BEANIS_MEMORY_NAMESPACE` (default `default`; `auto` gives each git project its own namespace), `BEANIS_EMBED_MODEL` (default `BAAI/bge-small-en-v1.5`).
204
+
205
+ ### As an MCP server
206
+
207
+ `beanis-mcp` (or `beanis mcp`) serves the same memory over MCP stdio with four tools: `remember`, `recall`, `forget`, `recent`.
208
+
209
+ Claude Code, as a plugin (MCP server plus a skill that teaches Claude the Beanis API):
210
+
211
+ ```bash
212
+ claude plugin marketplace add andreim14/beanis
213
+ claude plugin install beanis@beanis
214
+ ```
215
+
216
+ The plugin gives each project its own namespace and, at the start of every session, puts that project's newest memories into Claude's context, so a new session does not start from zero. Sessions and subagents share the store: what one remembers, the next one knows. It starts everything with `uvx`, so it needs [uv](https://docs.astral.sh/uv/) and a Redis 8 at `REDIS_URL`. Without the plugin:
217
+
218
+ ```bash
219
+ claude mcp add beanis-memory --env REDIS_URL=redis://localhost:6379/0 -- beanis-mcp
220
+ ```
221
+
222
+ Claude Desktop, Cursor and other MCP clients:
223
+
224
+ ```json
225
+ {
226
+ "mcpServers": {
227
+ "beanis-memory": {
228
+ "command": "beanis-mcp",
229
+ "env": { "REDIS_URL": "redis://localhost:6379/0" }
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ ## More
236
+
237
+ ```python
238
+ await product.insert(ttl=3600) # expire after an hour
239
+ await product.set_ttl(7200); await product.get_ttl(); await product.persist()
240
+
241
+ price = await product.get_field("price") # read or write one field
242
+ await product.set_field("stock", 200)
243
+
244
+ await Product.insert_many(products) # pipelined batches
245
+ await Product.get_many(ids)
246
+ await Product.delete_many(ids)
247
+ ```
248
+
249
+ Event hooks run around inserts, updates and deletes:
250
+
251
+ ```python
252
+ from beanis import Document, Insert, before_event
253
+
254
+
255
+ class Product(Document):
256
+ name: str
257
+ price: float
258
+
259
+ @before_event(Insert)
260
+ def check_price(self):
261
+ if self.price < 0:
262
+ raise ValueError("price cannot be negative")
263
+ ```
264
+
265
+ Custom types are registered once with `register_type(cls, encoder=..., decoder=...)`; NumPy arrays are supported when NumPy is installed. See the [custom encoders tutorial](docs/tutorial/custom-encoders.md).
266
+
267
+ ## When to use it
268
+
269
+ Use Beanis when you want typed models on Redis without running Redis modules: caches with structure, session and user state, job metadata, small catalogues, retrieval for RAG, and agent memory.
270
+
271
+ Use something else when you need complex queries across many fields, full-text search or aggregations: a document database, or Redis's query engine with [Redis OM](https://github.com/redis/redis-om-python) or [RedisVL](https://github.com/redis/redis-vl-python).
272
+
273
+ ## Requirements
274
+
275
+ - Python 3.9+ (3.10+ for the `mcp` extra)
276
+ - Redis 5.0+; Redis 8.0+ for `VectorField`, `Memory`, the CLI memory commands and the MCP server
277
+ - Pydantic 1.10+ or 2.x
278
+
279
+ ## Development
280
+
281
+ ```bash
282
+ pip install -e ".[test,mcp]"
283
+ pytest # unit tests, on fakeredis
284
+ REDIS_PORT=6379 pytest tests/test_real_redis_regressions.py tests/test_memory_real_redis.py # needs Redis 8
285
+ ```
286
+
287
+ See [AGENTS.md](AGENTS.md) for repository conventions.
288
+
289
+ ## Credits
290
+
291
+ Beanis started as a fork of [Beanie](https://github.com/BeanieODM/beanie), the MongoDB ODM by Roman Right and contributors, rewritten for Redis.
292
+
293
+ ## License
294
+
295
+ Apache License 2.0
296
+
beanis-0.3.2/README.md ADDED
@@ -0,0 +1,249 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/andreim14/beanis/main/assets/logo/beanis-dark.svg">
4
+ <img src="https://raw.githubusercontent.com/andreim14/beanis/main/assets/logo/beanis.svg" alt="beanis" width="420">
5
+ </picture>
6
+ </p>
7
+
8
+ <div align="center">
9
+ <a href="https://pypi.python.org/pypi/beanis"><img src="https://img.shields.io/pypi/v/beanis?color=blue" alt="PyPI version"></a>
10
+ <a href="https://pypi.python.org/pypi/beanis"><img src="https://img.shields.io/pypi/pyversions/beanis?color=blue" alt="Python versions"></a>
11
+ <a href="https://github.com/andreim14/beanis/blob/main/LICENSE"><img src="https://img.shields.io/github/license/andreim14/beanis?color=blue" alt="License"></a>
12
+ </div>
13
+
14
+ Beanis is an async object-document mapper for Redis, built on Pydantic. You define a model, and Beanis stores each instance as a Redis hash, keeps secondary indexes up to date, and gives you typed objects back.
15
+
16
+ It also does vector search on the vector sets that ship with Redis 8, and it includes long-term memory for AI agents, usable from Python, from the `beanis` command line, or as an MCP server for Claude Code and other agents.
17
+
18
+ It works on plain Redis. No RedisJSON, RediSearch or Redis Stack.
19
+
20
+ [Documentation](https://andreim14.github.io/beanis/) · [PyPI](https://pypi.org/project/beanis/) · [Changelog](docs/changelog.md)
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pip install beanis # the ODM
26
+ pip install "beanis[mcp]" # plus agent memory: CLI embeddings and MCP server (Python 3.10+)
27
+ ```
28
+
29
+ You need a Redis server. Redis 5+ is enough for documents and indexes; vector search and memory need Redis 8.0+:
30
+
31
+ ```bash
32
+ docker run -d -p 6379:6379 redis:8 # or: brew install redis
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ import asyncio
39
+
40
+ from pydantic import BaseModel
41
+ from redis.asyncio import Redis
42
+
43
+ from beanis import Document, Indexed, init_beanis
44
+
45
+
46
+ class Category(BaseModel):
47
+ name: str
48
+ description: str
49
+
50
+
51
+ class Product(Document):
52
+ name: str
53
+ price: Indexed(float) # sorted-set index: range queries
54
+ category: Indexed(str) # set index: exact match
55
+ tags: list[str] = []
56
+ stock: int = 0
57
+
58
+ class Settings:
59
+ key_prefix = "Product"
60
+
61
+
62
+ async def main():
63
+ redis = Redis(decode_responses=True)
64
+ await init_beanis(database=redis, document_models=[Product])
65
+
66
+ bar = Product(name="Dark chocolate", price=5.95, category="chocolate", stock=100)
67
+ await bar.insert()
68
+
69
+ same = await Product.get(bar.id)
70
+ cheap = await Product.find(price__lt=10)
71
+ chocolate = await Product.find(category="chocolate", price__gte=5)
72
+
73
+ await bar.update(price=6.50)
74
+ await bar.increment_field("stock", -1)
75
+ await bar.delete_self()
76
+
77
+
78
+ asyncio.run(main())
79
+ ```
80
+
81
+ `decode_responses=True` is required. Each document is the hash `Product:<id>`; nested models, lists, dicts, `datetime`, `Decimal`, `UUID` and enums are encoded for you and decoded on read.
82
+
83
+ ## Queries
84
+
85
+ `find()` takes keyword filters on indexed fields. Several filters are combined with AND.
86
+
87
+ ```python
88
+ await Product.find(category="chocolate") # exact match
89
+ await Product.find(price__gte=10, price__lte=50) # range: __gte, __lte, __gt, __lt
90
+ await Product.find(price=12.0) # exact match on a number
91
+ await Product.all(skip=0, limit=20) # every document, by insertion time
92
+ await Product.count()
93
+ ```
94
+
95
+ Filtering on a field that is not indexed raises `ValueError`. Sorting by other fields and full-text search are not built in; sort the returned list in Python, or use Redis's query engine directly if you need them.
96
+
97
+ Declare indexes with `Indexed(type)` or `Annotated[type, IndexedField()]`. Numbers get a sorted set, strings and other scalars get a set, `GeoPoint` gets a geo index (see the [geo tutorial](docs/tutorial/geo-spatial.md)).
98
+
99
+ ## Vector search
100
+
101
+ Embeddings live in a Redis 8 vector set (`VADD` / `VSIM`), next to the document's short fields so you can filter a search:
102
+
103
+ ```python
104
+ from typing import List
105
+
106
+ from typing_extensions import Annotated
107
+
108
+ from beanis import Document, IndexedField, VectorField
109
+
110
+
111
+ class Chunk(Document):
112
+ text: str
113
+ source: Annotated[str, IndexedField()]
114
+ embedding: Annotated[List[float], VectorField(dimensions=384)]
115
+
116
+
117
+ await Chunk(text="Refunds take 5 days", source="faq", embedding=embed("Refunds take 5 days")).insert()
118
+
119
+ for chunk, score in await Chunk.vector_search(embed("how long is a refund?"), k=3, filter='.source == "faq"'):
120
+ print(round(score, 3), chunk.text) # score: cosine similarity in [0, 1]
121
+ ```
122
+
123
+ `embed` is any function from text to a list of floats. Vectors use HNSW with int8 quantization by default (`VectorField(quantization="NOQUANT")` keeps float32). Updating or deleting a document updates its vector. `init_beanis` raises at startup if a model has vector fields and the server has no vector sets (Redis 7, Valkey).
124
+
125
+ ## Memory for agents
126
+
127
+ `Memory` stores what an agent should keep between conversations and finds it again by meaning. Memories are scoped by namespace, user, session and kind; a new memory that is nearly identical to an old one replaces it instead of piling up.
128
+
129
+ ```python
130
+ from beanis import Memory
131
+ from beanis.memory import fastembed_embedder
132
+
133
+ memory = Memory(redis, embed=fastembed_embedder(), namespace="support-bot")
134
+
135
+ await memory.remember("Prefers answers in Italian", user_id="u42", kind="preference")
136
+ for record, score in await memory.recall("which language should I answer in?", user_id="u42"):
137
+ print(round(score, 3), record.text)
138
+ await memory.recent(user_id="u42")
139
+ await memory.forget(record.id)
140
+ ```
141
+
142
+ `fastembed_embedder()` runs `BAAI/bge-small-en-v1.5` locally on the CPU (no API key). Any embedding function works, sync or async; use the same one for every writer of a namespace.
143
+
144
+ ### From the command line
145
+
146
+ `pip install "beanis[mcp]"` installs a `beanis` command. Every command prints JSON, so shell scripts and coding agents can use it directly.
147
+
148
+ ```bash
149
+ beanis doctor # checks Redis, vector sets, extras, stored memory
150
+ beanis memory remember "Deploys go out on Tuesdays" --kind decision
151
+ beanis memory recall "when do we deploy?" -k 3
152
+ beanis memory recent --limit 5
153
+ beanis memory forget <id>
154
+ beanis context # newest memories as plain text, for session hooks
155
+ ```
156
+
157
+ Configuration is by environment: `REDIS_URL` (default `redis://localhost:6379/0`, or pass `--url`), `BEANIS_MEMORY_NAMESPACE` (default `default`; `auto` gives each git project its own namespace), `BEANIS_EMBED_MODEL` (default `BAAI/bge-small-en-v1.5`).
158
+
159
+ ### As an MCP server
160
+
161
+ `beanis-mcp` (or `beanis mcp`) serves the same memory over MCP stdio with four tools: `remember`, `recall`, `forget`, `recent`.
162
+
163
+ Claude Code, as a plugin (MCP server plus a skill that teaches Claude the Beanis API):
164
+
165
+ ```bash
166
+ claude plugin marketplace add andreim14/beanis
167
+ claude plugin install beanis@beanis
168
+ ```
169
+
170
+ The plugin gives each project its own namespace and, at the start of every session, puts that project's newest memories into Claude's context, so a new session does not start from zero. Sessions and subagents share the store: what one remembers, the next one knows. It starts everything with `uvx`, so it needs [uv](https://docs.astral.sh/uv/) and a Redis 8 at `REDIS_URL`. Without the plugin:
171
+
172
+ ```bash
173
+ claude mcp add beanis-memory --env REDIS_URL=redis://localhost:6379/0 -- beanis-mcp
174
+ ```
175
+
176
+ Claude Desktop, Cursor and other MCP clients:
177
+
178
+ ```json
179
+ {
180
+ "mcpServers": {
181
+ "beanis-memory": {
182
+ "command": "beanis-mcp",
183
+ "env": { "REDIS_URL": "redis://localhost:6379/0" }
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ ## More
190
+
191
+ ```python
192
+ await product.insert(ttl=3600) # expire after an hour
193
+ await product.set_ttl(7200); await product.get_ttl(); await product.persist()
194
+
195
+ price = await product.get_field("price") # read or write one field
196
+ await product.set_field("stock", 200)
197
+
198
+ await Product.insert_many(products) # pipelined batches
199
+ await Product.get_many(ids)
200
+ await Product.delete_many(ids)
201
+ ```
202
+
203
+ Event hooks run around inserts, updates and deletes:
204
+
205
+ ```python
206
+ from beanis import Document, Insert, before_event
207
+
208
+
209
+ class Product(Document):
210
+ name: str
211
+ price: float
212
+
213
+ @before_event(Insert)
214
+ def check_price(self):
215
+ if self.price < 0:
216
+ raise ValueError("price cannot be negative")
217
+ ```
218
+
219
+ Custom types are registered once with `register_type(cls, encoder=..., decoder=...)`; NumPy arrays are supported when NumPy is installed. See the [custom encoders tutorial](docs/tutorial/custom-encoders.md).
220
+
221
+ ## When to use it
222
+
223
+ Use Beanis when you want typed models on Redis without running Redis modules: caches with structure, session and user state, job metadata, small catalogues, retrieval for RAG, and agent memory.
224
+
225
+ Use something else when you need complex queries across many fields, full-text search or aggregations: a document database, or Redis's query engine with [Redis OM](https://github.com/redis/redis-om-python) or [RedisVL](https://github.com/redis/redis-vl-python).
226
+
227
+ ## Requirements
228
+
229
+ - Python 3.9+ (3.10+ for the `mcp` extra)
230
+ - Redis 5.0+; Redis 8.0+ for `VectorField`, `Memory`, the CLI memory commands and the MCP server
231
+ - Pydantic 1.10+ or 2.x
232
+
233
+ ## Development
234
+
235
+ ```bash
236
+ pip install -e ".[test,mcp]"
237
+ pytest # unit tests, on fakeredis
238
+ REDIS_PORT=6379 pytest tests/test_real_redis_regressions.py tests/test_memory_real_redis.py # needs Redis 8
239
+ ```
240
+
241
+ See [AGENTS.md](AGENTS.md) for repository conventions.
242
+
243
+ ## Credits
244
+
245
+ Beanis started as a fork of [Beanie](https://github.com/BeanieODM/beanie), the MongoDB ODM by Roman Right and contributors, rewritten for Redis.
246
+
247
+ ## License
248
+
249
+ Apache License 2.0
@@ -25,7 +25,7 @@ from beanis.odm.documents import (
25
25
  from beanis.odm.indexes import GeoPoint, Indexed, IndexedField, VectorField
26
26
  from beanis.odm.utils.init import init_beanis
27
27
 
28
- __version__ = "0.3.0"
28
+ __version__ = "0.3.2"
29
29
  __all__ = [
30
30
  # ODM
31
31
  "Document",