memnos-sdk 1.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.
- memnos_sdk-1.1.0/PKG-INFO +13 -0
- memnos_sdk-1.1.0/README.md +241 -0
- memnos_sdk-1.1.0/memnos_sdk/__init__.py +52 -0
- memnos_sdk-1.1.0/memnos_sdk/_http.py +96 -0
- memnos_sdk-1.1.0/memnos_sdk/client.py +418 -0
- memnos_sdk-1.1.0/memnos_sdk/corpus.py +279 -0
- memnos_sdk-1.1.0/memnos_sdk/exceptions.py +25 -0
- memnos_sdk-1.1.0/memnos_sdk/integrations/__init__.py +0 -0
- memnos_sdk-1.1.0/memnos_sdk/integrations/langchain.py +71 -0
- memnos_sdk-1.1.0/memnos_sdk/integrations/llamaindex.py +51 -0
- memnos_sdk-1.1.0/memnos_sdk/models.py +116 -0
- memnos_sdk-1.1.0/memnos_sdk.egg-info/PKG-INFO +13 -0
- memnos_sdk-1.1.0/memnos_sdk.egg-info/SOURCES.txt +16 -0
- memnos_sdk-1.1.0/memnos_sdk.egg-info/dependency_links.txt +1 -0
- memnos_sdk-1.1.0/memnos_sdk.egg-info/requires.txt +11 -0
- memnos_sdk-1.1.0/memnos_sdk.egg-info/top_level.txt +1 -0
- memnos_sdk-1.1.0/pyproject.toml +22 -0
- memnos_sdk-1.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memnos-sdk
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: memnos SDK — memory layer for AI agents
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Requires-Dist: pydantic>=2.0
|
|
8
|
+
Provides-Extra: langchain
|
|
9
|
+
Requires-Dist: langchain-core>=0.2; extra == "langchain"
|
|
10
|
+
Provides-Extra: llamaindex
|
|
11
|
+
Requires-Dist: llama-index-core>=0.10; extra == "llamaindex"
|
|
12
|
+
Provides-Extra: all
|
|
13
|
+
Requires-Dist: memnos-sdk[langchain,llamaindex]; extra == "all"
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# memnos-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for [memnos](https://github.com/thameema/memnos) — persistent memory for AI agents.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from memnos_sdk import MemnosClient
|
|
7
|
+
|
|
8
|
+
client = MemnosClient(base_url="http://localhost:8766", api_key="your-key")
|
|
9
|
+
|
|
10
|
+
client.write("Chose PostgreSQL for the event store — ACID guarantees required",
|
|
11
|
+
namespace="org:myproject",
|
|
12
|
+
memory_type="decision",
|
|
13
|
+
affects=["event-store", "payment-service"],
|
|
14
|
+
rationale="Transactional writes across tables need ACID; Redis/Kafka can't provide this.")
|
|
15
|
+
|
|
16
|
+
results = client.search("event store choice", namespace="org:myproject")
|
|
17
|
+
for r in results:
|
|
18
|
+
print(r.content)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install memnos-sdk
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
With LangChain or LlamaIndex integrations:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install "memnos-sdk[langchain]"
|
|
31
|
+
pip install "memnos-sdk[llamaindex]"
|
|
32
|
+
pip install "memnos-sdk[all]"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.10+.
|
|
36
|
+
|
|
37
|
+
## Quick start
|
|
38
|
+
|
|
39
|
+
### Sync client
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from memnos_sdk import MemnosClient
|
|
43
|
+
|
|
44
|
+
client = MemnosClient(
|
|
45
|
+
base_url="http://localhost:8766",
|
|
46
|
+
api_key="memnos-local-dev-key",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Write a memory
|
|
50
|
+
mem = client.write(
|
|
51
|
+
content="Use ArcadeDB as the sole graph store — single process handles vector + graph + docs",
|
|
52
|
+
namespace="org:myproject",
|
|
53
|
+
memory_type="decision",
|
|
54
|
+
affects=["storage", "arcadedb"],
|
|
55
|
+
rationale="Eliminates three-service ops complexity (Neo4j + Qdrant + Graphiti).",
|
|
56
|
+
tags=["architecture", "adr"],
|
|
57
|
+
)
|
|
58
|
+
print(mem.id)
|
|
59
|
+
|
|
60
|
+
# Search across all accessible namespaces
|
|
61
|
+
results = client.search("storage architecture decision")
|
|
62
|
+
for r in results:
|
|
63
|
+
print(f"[{r.score:.2f}] {r.content[:80]}")
|
|
64
|
+
|
|
65
|
+
# Get a specific memory
|
|
66
|
+
mem = client.get(mem.id)
|
|
67
|
+
|
|
68
|
+
# Delete a memory
|
|
69
|
+
client.delete(mem.id)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Async client
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import asyncio
|
|
76
|
+
from memnos_sdk import AsyncMemnosClient
|
|
77
|
+
|
|
78
|
+
async def main():
|
|
79
|
+
async with AsyncMemnosClient(base_url="http://localhost:8766", api_key="key") as client:
|
|
80
|
+
await client.write("Chose gRPC for inter-service comms", namespace="org:svc",
|
|
81
|
+
memory_type="decision", affects=["api-gateway"])
|
|
82
|
+
results = await client.search("gRPC decision")
|
|
83
|
+
for r in results:
|
|
84
|
+
print(r.content)
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Memory types
|
|
90
|
+
|
|
91
|
+
| Type | When to use |
|
|
92
|
+
|------|-------------|
|
|
93
|
+
| `decision` | Architectural choices — what was decided and why |
|
|
94
|
+
| `constraint` | Rules that apply to a component — enforced at code review |
|
|
95
|
+
| `adr` | Architecture Decision Records |
|
|
96
|
+
| `fact` | Context, session notes, anything else |
|
|
97
|
+
| `session` | Automated session summaries from hooks |
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from memnos_sdk import MemoryType
|
|
101
|
+
|
|
102
|
+
client.write("...", namespace="org:x", memory_type=MemoryType.DECISION)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Corpus — architecture docs as a CI gate
|
|
106
|
+
|
|
107
|
+
Register a folder of markdown ADRs/decision docs and query them as constraints:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from memnos_sdk import MemnosClient
|
|
111
|
+
|
|
112
|
+
client = MemnosClient(base_url="http://localhost:8766", api_key="key")
|
|
113
|
+
|
|
114
|
+
# Register a corpus (one-time setup)
|
|
115
|
+
corpus = client.corpus.register(
|
|
116
|
+
name="backend-adrs",
|
|
117
|
+
source="./docs/architecture",
|
|
118
|
+
namespace="org:myproject",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Check a code change against registered constraints
|
|
122
|
+
result = client.corpus.check(
|
|
123
|
+
corpus_id=corpus.id,
|
|
124
|
+
component="payment-service",
|
|
125
|
+
code_snippet="db.execute('INSERT INTO payments ...')",
|
|
126
|
+
)
|
|
127
|
+
for hit in result.violations:
|
|
128
|
+
print(f"[{hit.severity}] {hit.rule}")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## LangChain integration
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from langchain_openai import ChatOpenAI
|
|
135
|
+
from langchain.chains import ConversationChain
|
|
136
|
+
from memnos_sdk.integrations.langchain import MemnosMemory
|
|
137
|
+
|
|
138
|
+
memory = MemnosMemory(
|
|
139
|
+
base_url="http://localhost:8766",
|
|
140
|
+
api_key="your-key",
|
|
141
|
+
namespace="org:myproject",
|
|
142
|
+
top_k=5,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
chain = ConversationChain(llm=ChatOpenAI(), memory=memory)
|
|
146
|
+
response = chain.predict(input="What storage decisions have we made?")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`MemnosMemory` loads the top-k most relevant memories as conversation context and writes new exchanges back to memnos automatically.
|
|
150
|
+
|
|
151
|
+
## LlamaIndex integration
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from llama_index.core import VectorStoreIndex
|
|
155
|
+
from memnos_sdk.integrations.llamaindex import MemnosReader
|
|
156
|
+
|
|
157
|
+
reader = MemnosReader(base_url="http://localhost:8766", api_key="your-key")
|
|
158
|
+
documents = reader.load_data(namespace="org:myproject", query="architecture decisions", top_k=20)
|
|
159
|
+
|
|
160
|
+
index = VectorStoreIndex.from_documents(documents)
|
|
161
|
+
query_engine = index.as_query_engine()
|
|
162
|
+
print(query_engine.query("Which storage system did we pick and why?"))
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Governance queries
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# Get all decisions governing a component
|
|
169
|
+
decisions = client.get_governing_decisions(namespace="org:myproject", component="auth-service")
|
|
170
|
+
for d in decisions:
|
|
171
|
+
print(f"[{d.memory_type}] {d.content[:80]}")
|
|
172
|
+
print(f" WHY: {d.rationale}")
|
|
173
|
+
|
|
174
|
+
# Get all constraints in a namespace
|
|
175
|
+
constraints = client.get_constraints(namespace="org:myproject")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Namespace export / import
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
# Export
|
|
182
|
+
data = client.export_namespace("org:myproject")
|
|
183
|
+
import json
|
|
184
|
+
json.dump(data, open("backup.json", "w"), indent=2)
|
|
185
|
+
|
|
186
|
+
# Import into a new namespace
|
|
187
|
+
with open("backup.json") as f:
|
|
188
|
+
client.import_namespace("org:newproject", json.load(f))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Error handling
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from memnos_sdk.exceptions import NotFoundError, AuthenticationError, MemnosError
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
mem = client.get("nonexistent-id")
|
|
198
|
+
except NotFoundError:
|
|
199
|
+
print("Memory not found")
|
|
200
|
+
except AuthenticationError:
|
|
201
|
+
print("Check your API key")
|
|
202
|
+
except MemnosError as e:
|
|
203
|
+
print(f"Memnos error: {e}")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
| Exception | When raised |
|
|
207
|
+
|-----------|-------------|
|
|
208
|
+
| `MemnosError` | Base class for all SDK errors |
|
|
209
|
+
| `AuthenticationError` | Invalid or missing API key |
|
|
210
|
+
| `NotFoundError` | Memory or corpus not found |
|
|
211
|
+
| `ValidationError` | Malformed request (bad namespace, etc.) |
|
|
212
|
+
| `ServerError` | 5xx from the memnos server |
|
|
213
|
+
| `ConnectionError` | Server unreachable |
|
|
214
|
+
|
|
215
|
+
## Configuration
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
client = MemnosClient(
|
|
219
|
+
base_url="http://localhost:8766", # or MEMNOS_API env var
|
|
220
|
+
api_key="your-key", # or MEMNOS_KEY env var
|
|
221
|
+
timeout=15.0, # per-request timeout in seconds
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Environment variables:
|
|
226
|
+
|
|
227
|
+
| Variable | Default | Description |
|
|
228
|
+
|----------|---------|-------------|
|
|
229
|
+
| `MEMNOS_API` | `http://localhost:8766` | Server URL |
|
|
230
|
+
| `MEMNOS_KEY` | — | API key |
|
|
231
|
+
|
|
232
|
+
## Running memnos locally
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
git clone https://github.com/thameema/memnos
|
|
236
|
+
cd memnos
|
|
237
|
+
docker compose up -d
|
|
238
|
+
# Server is at http://localhost:8766, key: memnos-local-dev-key
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
See the [main README](../../README.md) for full setup.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""memnos-sdk — memory layer for AI agents."""
|
|
2
|
+
from memnos_sdk.client import AsyncMemnosClient, MemnosClient
|
|
3
|
+
from memnos_sdk.models import (
|
|
4
|
+
Memory,
|
|
5
|
+
MemoryType,
|
|
6
|
+
SearchResult,
|
|
7
|
+
HealthStatus,
|
|
8
|
+
CorpusInfo,
|
|
9
|
+
CorpusStatus,
|
|
10
|
+
ConstraintHit,
|
|
11
|
+
CheckResult,
|
|
12
|
+
)
|
|
13
|
+
from memnos_sdk.corpus import AsyncCorpusClient, SyncCorpusClient
|
|
14
|
+
from memnos_sdk.exceptions import (
|
|
15
|
+
MemnosError,
|
|
16
|
+
AuthenticationError,
|
|
17
|
+
NotFoundError,
|
|
18
|
+
ValidationError,
|
|
19
|
+
ServerError,
|
|
20
|
+
ConnectionError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = "1.1.0"
|
|
24
|
+
SCHEMA_VERSION = "1.0"
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
# Clients
|
|
28
|
+
"MemnosClient",
|
|
29
|
+
"AsyncMemnosClient",
|
|
30
|
+
# Memory models
|
|
31
|
+
"Memory",
|
|
32
|
+
"MemoryType",
|
|
33
|
+
"SearchResult",
|
|
34
|
+
"HealthStatus",
|
|
35
|
+
# Corpus models
|
|
36
|
+
"CorpusInfo",
|
|
37
|
+
"CorpusStatus",
|
|
38
|
+
"ConstraintHit",
|
|
39
|
+
"CheckResult",
|
|
40
|
+
# Corpus sub-clients (advanced use)
|
|
41
|
+
"AsyncCorpusClient",
|
|
42
|
+
"SyncCorpusClient",
|
|
43
|
+
# Exceptions
|
|
44
|
+
"MemnosError",
|
|
45
|
+
"AuthenticationError",
|
|
46
|
+
"NotFoundError",
|
|
47
|
+
"ValidationError",
|
|
48
|
+
"ServerError",
|
|
49
|
+
"ConnectionError",
|
|
50
|
+
"__version__",
|
|
51
|
+
"SCHEMA_VERSION",
|
|
52
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from memnos_sdk.exceptions import (
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
ConnectionError,
|
|
10
|
+
NotFoundError,
|
|
11
|
+
ServerError,
|
|
12
|
+
ValidationError,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _raise_for_status(response: httpx.Response) -> None:
|
|
17
|
+
code = response.status_code
|
|
18
|
+
if code in (401, 403):
|
|
19
|
+
raise AuthenticationError(f"HTTP {code}: {response.text}")
|
|
20
|
+
if code == 404:
|
|
21
|
+
raise NotFoundError(f"HTTP 404: {response.text}")
|
|
22
|
+
if code == 422:
|
|
23
|
+
raise ValidationError(f"HTTP 422: {response.text}")
|
|
24
|
+
if code >= 500:
|
|
25
|
+
raise ServerError(f"HTTP {code}: {response.text}")
|
|
26
|
+
response.raise_for_status()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _SyncTransport:
|
|
30
|
+
def __init__(self, base_url: str, api_key: str, timeout: float = 30.0) -> None:
|
|
31
|
+
self._client = httpx.Client(
|
|
32
|
+
base_url=base_url,
|
|
33
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
34
|
+
timeout=timeout,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def get(self, path: str, params: dict[str, Any] | None = None) -> dict:
|
|
38
|
+
try:
|
|
39
|
+
response = self._client.get(path, params=params)
|
|
40
|
+
except httpx.TransportError as exc:
|
|
41
|
+
raise ConnectionError(str(exc)) from exc
|
|
42
|
+
_raise_for_status(response)
|
|
43
|
+
return response.json()
|
|
44
|
+
|
|
45
|
+
def post(self, path: str, json: dict | None = None) -> dict:
|
|
46
|
+
try:
|
|
47
|
+
response = self._client.post(path, json=json)
|
|
48
|
+
except httpx.TransportError as exc:
|
|
49
|
+
raise ConnectionError(str(exc)) from exc
|
|
50
|
+
_raise_for_status(response)
|
|
51
|
+
return response.json()
|
|
52
|
+
|
|
53
|
+
def delete(self, path: str) -> None:
|
|
54
|
+
try:
|
|
55
|
+
response = self._client.delete(path)
|
|
56
|
+
except httpx.TransportError as exc:
|
|
57
|
+
raise ConnectionError(str(exc)) from exc
|
|
58
|
+
_raise_for_status(response)
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
self._client.close()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _AsyncTransport:
|
|
65
|
+
def __init__(self, base_url: str, api_key: str, timeout: float = 30.0) -> None:
|
|
66
|
+
self._client = httpx.AsyncClient(
|
|
67
|
+
base_url=base_url,
|
|
68
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
69
|
+
timeout=timeout,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async def get(self, path: str, params: dict[str, Any] | None = None) -> dict:
|
|
73
|
+
try:
|
|
74
|
+
response = await self._client.get(path, params=params)
|
|
75
|
+
except httpx.TransportError as exc:
|
|
76
|
+
raise ConnectionError(str(exc)) from exc
|
|
77
|
+
_raise_for_status(response)
|
|
78
|
+
return response.json()
|
|
79
|
+
|
|
80
|
+
async def post(self, path: str, json: dict | None = None) -> dict:
|
|
81
|
+
try:
|
|
82
|
+
response = await self._client.post(path, json=json)
|
|
83
|
+
except httpx.TransportError as exc:
|
|
84
|
+
raise ConnectionError(str(exc)) from exc
|
|
85
|
+
_raise_for_status(response)
|
|
86
|
+
return response.json()
|
|
87
|
+
|
|
88
|
+
async def delete(self, path: str) -> None:
|
|
89
|
+
try:
|
|
90
|
+
response = await self._client.delete(path)
|
|
91
|
+
except httpx.TransportError as exc:
|
|
92
|
+
raise ConnectionError(str(exc)) from exc
|
|
93
|
+
_raise_for_status(response)
|
|
94
|
+
|
|
95
|
+
async def aclose(self) -> None:
|
|
96
|
+
await self._client.aclose()
|