memos-ai 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.
- memos_ai-0.1.0/LICENSE +21 -0
- memos_ai-0.1.0/MANIFEST.in +19 -0
- memos_ai-0.1.0/PKG-INFO +120 -0
- memos_ai-0.1.0/README.md +103 -0
- memos_ai-0.1.0/memos/__init__.py +22 -0
- memos_ai-0.1.0/memos/client.py +337 -0
- memos_ai-0.1.0/memos/exceptions.py +27 -0
- memos_ai-0.1.0/memos/models.py +75 -0
- memos_ai-0.1.0/memos_ai.egg-info/PKG-INFO +120 -0
- memos_ai-0.1.0/memos_ai.egg-info/SOURCES.txt +14 -0
- memos_ai-0.1.0/memos_ai.egg-info/dependency_links.txt +1 -0
- memos_ai-0.1.0/memos_ai.egg-info/requires.txt +5 -0
- memos_ai-0.1.0/memos_ai.egg-info/top_level.txt +1 -0
- memos_ai-0.1.0/pyproject.toml +27 -0
- memos_ai-0.1.0/setup.cfg +4 -0
- memos_ai-0.1.0/tests/test_client.py +127 -0
memos_ai-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 memos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
|
|
5
|
+
global-exclude .env
|
|
6
|
+
global-exclude node_modules
|
|
7
|
+
global-exclude node_modules/*
|
|
8
|
+
global-exclude venv
|
|
9
|
+
global-exclude venv/*
|
|
10
|
+
global-exclude dist
|
|
11
|
+
global-exclude dist/*
|
|
12
|
+
global-exclude __pycache__
|
|
13
|
+
global-exclude __pycache__/*
|
|
14
|
+
global-exclude .pytest_cache
|
|
15
|
+
global-exclude .pytest_cache/*
|
|
16
|
+
global-exclude coverage
|
|
17
|
+
global-exclude coverage/*
|
|
18
|
+
global-exclude *.log
|
|
19
|
+
global-exclude *.sqlite
|
memos_ai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memos-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for memos — persistent brain framework for AI agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://memos.io
|
|
7
|
+
Project-URL: Repository, https://github.com/memos/memos-py
|
|
8
|
+
Project-URL: Documentation, https://docs.memos.io
|
|
9
|
+
Requires-Python: <4.0,>=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: httpx>=0.24.0
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
15
|
+
Requires-Dist: pytest-mock>=3.0; extra == "test"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# MEMOS Python SDK
|
|
19
|
+
|
|
20
|
+
Python SDK for [MEMOS](https://memos.io) — persistent brain framework for AI agents.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
```bash
|
|
24
|
+
pip install memos-ai
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Publish Verification
|
|
28
|
+
```bash
|
|
29
|
+
python3 -m venv verify
|
|
30
|
+
source verify/bin/activate
|
|
31
|
+
pip install memos-ai
|
|
32
|
+
python3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from memos import MemosClient
|
|
37
|
+
|
|
38
|
+
print(MemosClient)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Expected output:
|
|
42
|
+
```text
|
|
43
|
+
<class 'memos.client.MemosClient'>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
```python
|
|
48
|
+
from memos import MemosClient
|
|
49
|
+
|
|
50
|
+
client = MemosClient(
|
|
51
|
+
api_key="your_api_key",
|
|
52
|
+
agent_id="your_agent_id"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Store a memory
|
|
56
|
+
memory = client.store_memory(
|
|
57
|
+
content="User prefers Python over JavaScript",
|
|
58
|
+
type="semantic",
|
|
59
|
+
importance=4
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Search memories
|
|
63
|
+
results = client.search("language preferences")
|
|
64
|
+
for r in results:
|
|
65
|
+
print(f"{r.score:.2f} — {r.content}")
|
|
66
|
+
|
|
67
|
+
# Ask with memory context
|
|
68
|
+
response = client.query("What language does this user prefer?")
|
|
69
|
+
print(response.answer)
|
|
70
|
+
|
|
71
|
+
# Trigger dream consolidation
|
|
72
|
+
dream = client.trigger_dream()
|
|
73
|
+
print(f"Created {dream.new_memories_created} new memories")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Authentication
|
|
77
|
+
Get your API key and agent ID from https://memos.io/dashboard
|
|
78
|
+
|
|
79
|
+
## Methods Reference
|
|
80
|
+
|
|
81
|
+
| Method | Parameters | Returns | Description |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `store_memory` | `content` (str), `type` (str="episodic"), `importance` (int=3), `tags` (list[str]=None) | `Memory` | Store a new memory for the agent |
|
|
84
|
+
| `list_memories` | None | `list[Memory]` | List all memories for the agent |
|
|
85
|
+
| `delete_memory` | `memory_id` (str) | `bool` | Delete a specific memory by ID |
|
|
86
|
+
| `search` | `query` (str), `search_type` (str="keyword"), `limit` (int=10) | `list[SearchResult]` | Search agent memories |
|
|
87
|
+
| `query` | `question` (str), `include_sources` (bool=True), `conversation_history` (list[dict]=None) | `RAGResponse` | Ask a question using RAG |
|
|
88
|
+
| `trigger_dream` | None | `DreamResult` | Trigger a dream consolidation cycle |
|
|
89
|
+
| `list_skills` | None | `list[Skill]` | List all available skills in the marketplace |
|
|
90
|
+
| `execute_skill` | `skill_id` (str), `input` (str) | `SkillResult` | Execute a skill |
|
|
91
|
+
| `run_pipeline` | `steps` (list[dict]), `input` (str) | `dict` | Run a multi-step pipeline |
|
|
92
|
+
| `get_identity` | None | `dict` | Get the agent's identity and reputation |
|
|
93
|
+
|
|
94
|
+
## Error Handling
|
|
95
|
+
```python
|
|
96
|
+
from memos import MemosError, AuthError, RateLimitError
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
client.store_memory("...")
|
|
100
|
+
except AuthError:
|
|
101
|
+
print("Check your API key at memos.io/profile")
|
|
102
|
+
except RateLimitError:
|
|
103
|
+
print("Rate limit hit — slow down requests")
|
|
104
|
+
except MemosError as e:
|
|
105
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Context Manager
|
|
109
|
+
```python
|
|
110
|
+
with MemosClient(api_key="...", agent_id="...") as client:
|
|
111
|
+
client.store_memory("...")
|
|
112
|
+
# HTTP connection closed automatically
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Requirements
|
|
116
|
+
Python 3.8+
|
|
117
|
+
No additional dependencies beyond `httpx`.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
MIT
|
memos_ai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# MEMOS Python SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for [MEMOS](https://memos.io) — persistent brain framework for AI agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
pip install memos-ai
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Publish Verification
|
|
11
|
+
```bash
|
|
12
|
+
python3 -m venv verify
|
|
13
|
+
source verify/bin/activate
|
|
14
|
+
pip install memos-ai
|
|
15
|
+
python3
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from memos import MemosClient
|
|
20
|
+
|
|
21
|
+
print(MemosClient)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Expected output:
|
|
25
|
+
```text
|
|
26
|
+
<class 'memos.client.MemosClient'>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
```python
|
|
31
|
+
from memos import MemosClient
|
|
32
|
+
|
|
33
|
+
client = MemosClient(
|
|
34
|
+
api_key="your_api_key",
|
|
35
|
+
agent_id="your_agent_id"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Store a memory
|
|
39
|
+
memory = client.store_memory(
|
|
40
|
+
content="User prefers Python over JavaScript",
|
|
41
|
+
type="semantic",
|
|
42
|
+
importance=4
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Search memories
|
|
46
|
+
results = client.search("language preferences")
|
|
47
|
+
for r in results:
|
|
48
|
+
print(f"{r.score:.2f} — {r.content}")
|
|
49
|
+
|
|
50
|
+
# Ask with memory context
|
|
51
|
+
response = client.query("What language does this user prefer?")
|
|
52
|
+
print(response.answer)
|
|
53
|
+
|
|
54
|
+
# Trigger dream consolidation
|
|
55
|
+
dream = client.trigger_dream()
|
|
56
|
+
print(f"Created {dream.new_memories_created} new memories")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
Get your API key and agent ID from https://memos.io/dashboard
|
|
61
|
+
|
|
62
|
+
## Methods Reference
|
|
63
|
+
|
|
64
|
+
| Method | Parameters | Returns | Description |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| `store_memory` | `content` (str), `type` (str="episodic"), `importance` (int=3), `tags` (list[str]=None) | `Memory` | Store a new memory for the agent |
|
|
67
|
+
| `list_memories` | None | `list[Memory]` | List all memories for the agent |
|
|
68
|
+
| `delete_memory` | `memory_id` (str) | `bool` | Delete a specific memory by ID |
|
|
69
|
+
| `search` | `query` (str), `search_type` (str="keyword"), `limit` (int=10) | `list[SearchResult]` | Search agent memories |
|
|
70
|
+
| `query` | `question` (str), `include_sources` (bool=True), `conversation_history` (list[dict]=None) | `RAGResponse` | Ask a question using RAG |
|
|
71
|
+
| `trigger_dream` | None | `DreamResult` | Trigger a dream consolidation cycle |
|
|
72
|
+
| `list_skills` | None | `list[Skill]` | List all available skills in the marketplace |
|
|
73
|
+
| `execute_skill` | `skill_id` (str), `input` (str) | `SkillResult` | Execute a skill |
|
|
74
|
+
| `run_pipeline` | `steps` (list[dict]), `input` (str) | `dict` | Run a multi-step pipeline |
|
|
75
|
+
| `get_identity` | None | `dict` | Get the agent's identity and reputation |
|
|
76
|
+
|
|
77
|
+
## Error Handling
|
|
78
|
+
```python
|
|
79
|
+
from memos import MemosError, AuthError, RateLimitError
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
client.store_memory("...")
|
|
83
|
+
except AuthError:
|
|
84
|
+
print("Check your API key at memos.io/profile")
|
|
85
|
+
except RateLimitError:
|
|
86
|
+
print("Rate limit hit — slow down requests")
|
|
87
|
+
except MemosError as e:
|
|
88
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Context Manager
|
|
92
|
+
```python
|
|
93
|
+
with MemosClient(api_key="...", agent_id="...") as client:
|
|
94
|
+
client.store_memory("...")
|
|
95
|
+
# HTTP connection closed automatically
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
Python 3.8+
|
|
100
|
+
No additional dependencies beyond `httpx`.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""memos-py — Python SDK for memos."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .client import MemosClient
|
|
5
|
+
from .exceptions import AuthError, MemosError, NotFoundError, RateLimitError, ServerError
|
|
6
|
+
from .models import DreamResult, Memory, RAGResponse, SearchResult, Skill, SkillResult
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"MemosClient",
|
|
11
|
+
"Memory",
|
|
12
|
+
"SearchResult",
|
|
13
|
+
"RAGResponse",
|
|
14
|
+
"Skill",
|
|
15
|
+
"SkillResult",
|
|
16
|
+
"DreamResult",
|
|
17
|
+
"MemosError",
|
|
18
|
+
"AuthError",
|
|
19
|
+
"RateLimitError",
|
|
20
|
+
"NotFoundError",
|
|
21
|
+
"ServerError",
|
|
22
|
+
]
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Synchronous HTTP client for the memos API."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
from .exceptions import AuthError, MemosError, NotFoundError, RateLimitError, ServerError
|
|
9
|
+
from .models import DreamResult, Memory, RAGResponse, SearchResult, Skill, SkillResult
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MemosClient:
|
|
13
|
+
"""Python client for the memos API.
|
|
14
|
+
|
|
15
|
+
Usage::
|
|
16
|
+
|
|
17
|
+
from memos import MemosClient
|
|
18
|
+
|
|
19
|
+
client = MemosClient(
|
|
20
|
+
api_key="mk0s_your_key",
|
|
21
|
+
agent_id="your_agent_id",
|
|
22
|
+
base_url="https://memos.io"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
with MemosClient(api_key=..., agent_id=...) as client:
|
|
26
|
+
client.store_memory("...")
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
api_key: str,
|
|
32
|
+
agent_id: str,
|
|
33
|
+
base_url: str = "https://memos.io",
|
|
34
|
+
timeout: float = 30.0,
|
|
35
|
+
) -> None:
|
|
36
|
+
if not api_key or not api_key.strip():
|
|
37
|
+
raise AuthError("api_key is required", status_code=None)
|
|
38
|
+
if not agent_id or not agent_id.strip():
|
|
39
|
+
raise MemosError("agent_id is required", status_code=None)
|
|
40
|
+
|
|
41
|
+
self.api_key = api_key
|
|
42
|
+
self.agent_id = agent_id
|
|
43
|
+
self.base_url = base_url.rstrip("/")
|
|
44
|
+
self._client = httpx.Client(
|
|
45
|
+
base_url=self.base_url,
|
|
46
|
+
timeout=timeout,
|
|
47
|
+
headers={
|
|
48
|
+
"Authorization": "Bearer {0}".format(api_key),
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"User-Agent": "memos-py/0.1.0",
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Internal helpers
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
def _handle_response(self, response: httpx.Response) -> Dict: # type: ignore[type-arg]
|
|
59
|
+
"""Parse response and raise typed exceptions on errors."""
|
|
60
|
+
if response.status_code == 401:
|
|
61
|
+
raise AuthError("Invalid or missing API key", 401)
|
|
62
|
+
if response.status_code == 429:
|
|
63
|
+
raise RateLimitError("Rate limit exceeded", 429)
|
|
64
|
+
if response.status_code == 404:
|
|
65
|
+
raise NotFoundError("Resource not found", 404)
|
|
66
|
+
if response.status_code >= 500:
|
|
67
|
+
raise ServerError("Server error: {0}".format(response.text), response.status_code)
|
|
68
|
+
if not response.is_success:
|
|
69
|
+
raise MemosError("Request failed: {0}".format(response.text), response.status_code)
|
|
70
|
+
return response.json() # type: ignore[no-any-return]
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def _build_memory(data: Dict) -> Memory: # type: ignore[type-arg]
|
|
74
|
+
"""Safely build a Memory from a response dict, ignoring unknown keys."""
|
|
75
|
+
known = Memory.__dataclass_fields__
|
|
76
|
+
filtered = {k: v for k, v in data.items() if k in known}
|
|
77
|
+
# Map camelCase keys from API
|
|
78
|
+
if "agentId" in data and "agent_id" not in filtered:
|
|
79
|
+
filtered["agent_id"] = data["agentId"]
|
|
80
|
+
if "createdAt" in data and "created_at" not in filtered:
|
|
81
|
+
filtered["created_at"] = data["createdAt"]
|
|
82
|
+
if "accessCount" in data and "access_count" not in filtered:
|
|
83
|
+
filtered["access_count"] = data["accessCount"]
|
|
84
|
+
if "decayScore" in data and "decay_score" not in filtered:
|
|
85
|
+
filtered["decay_score"] = data["decayScore"]
|
|
86
|
+
return Memory(**filtered)
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# Public API
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
def store_memory(
|
|
93
|
+
self,
|
|
94
|
+
content: str,
|
|
95
|
+
type: str = "episodic",
|
|
96
|
+
importance: int = 3,
|
|
97
|
+
tags: Optional[List[str]] = None,
|
|
98
|
+
) -> Memory:
|
|
99
|
+
"""Store a new memory for the agent.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
content: The text content to store.
|
|
103
|
+
type: Memory type — "episodic", "semantic", or "procedural".
|
|
104
|
+
importance: Importance score from 1 to 5.
|
|
105
|
+
tags: Optional list of string tags.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
The created Memory object.
|
|
109
|
+
"""
|
|
110
|
+
response = self._client.post(
|
|
111
|
+
"/api/memory",
|
|
112
|
+
json={
|
|
113
|
+
"agentId": self.agent_id,
|
|
114
|
+
"content": content,
|
|
115
|
+
"type": type,
|
|
116
|
+
"importance": importance,
|
|
117
|
+
"tags": tags or [],
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
data = self._handle_response(response)
|
|
121
|
+
memory_data = data.get("memory", data)
|
|
122
|
+
return self._build_memory(memory_data)
|
|
123
|
+
|
|
124
|
+
def list_memories(self) -> List[Memory]:
|
|
125
|
+
"""List all memories for the agent.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A list of Memory objects.
|
|
129
|
+
"""
|
|
130
|
+
response = self._client.get(
|
|
131
|
+
"/api/memory",
|
|
132
|
+
params={"agentId": self.agent_id},
|
|
133
|
+
)
|
|
134
|
+
data = self._handle_response(response)
|
|
135
|
+
items = data.get("memories", data) if isinstance(data, dict) else data
|
|
136
|
+
if not isinstance(items, list):
|
|
137
|
+
items = []
|
|
138
|
+
return [self._build_memory(m) for m in items]
|
|
139
|
+
|
|
140
|
+
def delete_memory(self, memory_id: str) -> bool:
|
|
141
|
+
"""Delete a specific memory by ID.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
memory_id: The ID of the memory to delete.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if deletion was successful.
|
|
148
|
+
"""
|
|
149
|
+
response = self._client.delete("/api/memory/{0}".format(memory_id))
|
|
150
|
+
self._handle_response(response)
|
|
151
|
+
return True
|
|
152
|
+
|
|
153
|
+
def search(
|
|
154
|
+
self,
|
|
155
|
+
query: str,
|
|
156
|
+
search_type: str = "keyword",
|
|
157
|
+
limit: int = 10,
|
|
158
|
+
) -> List[SearchResult]:
|
|
159
|
+
"""Search agent memories.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
query: The search query string.
|
|
163
|
+
search_type: "keyword" or "semantic".
|
|
164
|
+
limit: Maximum number of results.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
A list of SearchResult objects.
|
|
168
|
+
"""
|
|
169
|
+
response = self._client.post(
|
|
170
|
+
"/api/search",
|
|
171
|
+
json={
|
|
172
|
+
"agentId": self.agent_id,
|
|
173
|
+
"query": query,
|
|
174
|
+
"searchType": search_type,
|
|
175
|
+
"limit": limit,
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
data = self._handle_response(response)
|
|
179
|
+
items = data.get("results", data) if isinstance(data, dict) else data
|
|
180
|
+
if not isinstance(items, list):
|
|
181
|
+
items = []
|
|
182
|
+
return [
|
|
183
|
+
SearchResult(
|
|
184
|
+
id=r.get("id", ""),
|
|
185
|
+
content=r.get("content", ""),
|
|
186
|
+
type=r.get("type", ""),
|
|
187
|
+
importance=r.get("importance", 0),
|
|
188
|
+
score=r.get("score", 0.0),
|
|
189
|
+
tags=r.get("tags", []),
|
|
190
|
+
)
|
|
191
|
+
for r in items
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
def query(
|
|
195
|
+
self,
|
|
196
|
+
question: str,
|
|
197
|
+
include_sources: bool = True,
|
|
198
|
+
conversation_history: Optional[List[dict]] = None, # type: ignore[type-arg]
|
|
199
|
+
) -> RAGResponse:
|
|
200
|
+
"""Ask a question using RAG (retrieval-augmented generation).
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
question: The question to ask.
|
|
204
|
+
include_sources: Whether to include source memories.
|
|
205
|
+
conversation_history: Optional prior conversation turns.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
A RAGResponse with answer, sources, and confidence.
|
|
209
|
+
"""
|
|
210
|
+
response = self._client.post(
|
|
211
|
+
"/api/rag",
|
|
212
|
+
json={
|
|
213
|
+
"agentId": self.agent_id,
|
|
214
|
+
"query": question,
|
|
215
|
+
"conversationHistory": conversation_history or [],
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
data = self._handle_response(response)
|
|
219
|
+
return RAGResponse(
|
|
220
|
+
answer=data.get("answer", ""),
|
|
221
|
+
sources=data.get("sources", []),
|
|
222
|
+
confidence=data.get("confidence", 0.0),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def trigger_dream(self) -> DreamResult:
|
|
226
|
+
"""Trigger a dream consolidation cycle for the agent.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
A DreamResult with consolidation details.
|
|
230
|
+
"""
|
|
231
|
+
response = self._client.post(
|
|
232
|
+
"/api/agent/{0}/dreams".format(self.agent_id),
|
|
233
|
+
json={},
|
|
234
|
+
)
|
|
235
|
+
data = self._handle_response(response)
|
|
236
|
+
return DreamResult(
|
|
237
|
+
memories_analyzed=data.get("memoriesAnalyzed", 0),
|
|
238
|
+
patterns_found=data.get("patternsFound", 0),
|
|
239
|
+
new_memories_created=data.get("newMemoriesCreated", 0),
|
|
240
|
+
dream_summary=data.get("dreamSummary", ""),
|
|
241
|
+
new_memories=data.get("newMemories", []),
|
|
242
|
+
duration=data.get("duration", 0),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def list_skills(self) -> List[Skill]:
|
|
246
|
+
"""List all available skills in the marketplace.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
A list of Skill objects.
|
|
250
|
+
"""
|
|
251
|
+
response = self._client.get("/api/skills")
|
|
252
|
+
data = self._handle_response(response)
|
|
253
|
+
items = data.get("skills", data) if isinstance(data, dict) else data
|
|
254
|
+
if not isinstance(items, list):
|
|
255
|
+
items = []
|
|
256
|
+
return [
|
|
257
|
+
Skill(
|
|
258
|
+
id=s.get("id", ""),
|
|
259
|
+
name=s.get("name", ""),
|
|
260
|
+
description=s.get("description", ""),
|
|
261
|
+
category=s.get("category", ""),
|
|
262
|
+
price=s.get("price", 0.0),
|
|
263
|
+
publisher=s.get("publisher", ""),
|
|
264
|
+
)
|
|
265
|
+
for s in items
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
def execute_skill(self, skill_id: str, input: str) -> SkillResult:
|
|
269
|
+
"""Execute a skill.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
skill_id: The ID of the skill to execute.
|
|
273
|
+
input: The input string for the skill.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
A SkillResult with execution details.
|
|
277
|
+
"""
|
|
278
|
+
response = self._client.post(
|
|
279
|
+
"/api/execute",
|
|
280
|
+
json={
|
|
281
|
+
"agentId": self.agent_id,
|
|
282
|
+
"skillId": skill_id,
|
|
283
|
+
"input": input,
|
|
284
|
+
},
|
|
285
|
+
)
|
|
286
|
+
data = self._handle_response(response)
|
|
287
|
+
return SkillResult(
|
|
288
|
+
skill_id=data.get("skillId", skill_id),
|
|
289
|
+
result=data.get("result", ""),
|
|
290
|
+
tokens_used=data.get("tokensUsed", 0),
|
|
291
|
+
duration=data.get("duration", 0),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def run_pipeline(self, steps: List[dict], input: str) -> dict: # type: ignore[type-arg]
|
|
295
|
+
"""Run a multi-step pipeline.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
steps: List of step dicts, each with at least a "skillId" key.
|
|
299
|
+
input: The initial input string.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Raw response dict (pipeline responses are complex).
|
|
303
|
+
"""
|
|
304
|
+
response = self._client.post(
|
|
305
|
+
"/api/pipeline",
|
|
306
|
+
json={
|
|
307
|
+
"agentId": self.agent_id,
|
|
308
|
+
"steps": steps,
|
|
309
|
+
"input": input,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
return self._handle_response(response)
|
|
313
|
+
|
|
314
|
+
def get_identity(self) -> dict: # type: ignore[type-arg]
|
|
315
|
+
"""Get the agent's identity and reputation.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Raw response dict with reputation data.
|
|
319
|
+
"""
|
|
320
|
+
response = self._client.get(
|
|
321
|
+
"/api/agent/{0}/reputation".format(self.agent_id),
|
|
322
|
+
)
|
|
323
|
+
return self._handle_response(response)
|
|
324
|
+
|
|
325
|
+
# ------------------------------------------------------------------
|
|
326
|
+
# Lifecycle
|
|
327
|
+
# ------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
def close(self) -> None:
|
|
330
|
+
"""Close the underlying HTTP connection."""
|
|
331
|
+
self._client.close()
|
|
332
|
+
|
|
333
|
+
def __enter__(self) -> "MemosClient":
|
|
334
|
+
return self
|
|
335
|
+
|
|
336
|
+
def __exit__(self, *args: object) -> None:
|
|
337
|
+
self.close()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""memos-py exception hierarchy."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MemosError(Exception):
|
|
6
|
+
"""Base exception for all memos SDK errors."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, message: str, status_code: int | None = None) -> None:
|
|
9
|
+
self.message = message
|
|
10
|
+
self.status_code = status_code
|
|
11
|
+
super().__init__(message)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthError(MemosError):
|
|
15
|
+
"""Raised when authentication fails (HTTP 401)."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RateLimitError(MemosError):
|
|
19
|
+
"""Raised when rate limit is exceeded (HTTP 429)."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NotFoundError(MemosError):
|
|
23
|
+
"""Raised when a resource is not found (HTTP 404)."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ServerError(MemosError):
|
|
27
|
+
"""Raised when the server returns a 5xx error."""
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""memos-py data models using Python dataclasses."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Memory:
|
|
10
|
+
"""Represents a stored memory."""
|
|
11
|
+
|
|
12
|
+
id: str
|
|
13
|
+
agent_id: str
|
|
14
|
+
content: str
|
|
15
|
+
type: str
|
|
16
|
+
importance: int
|
|
17
|
+
tags: List[str] = field(default_factory=list)
|
|
18
|
+
created_at: Optional[str] = None
|
|
19
|
+
access_count: int = 0
|
|
20
|
+
decay_score: float = 1.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class SearchResult:
|
|
25
|
+
"""A single search result."""
|
|
26
|
+
|
|
27
|
+
id: str
|
|
28
|
+
content: str
|
|
29
|
+
type: str
|
|
30
|
+
importance: int
|
|
31
|
+
score: float
|
|
32
|
+
tags: List[str] = field(default_factory=list)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class RAGResponse:
|
|
37
|
+
"""Response from a RAG (retrieval-augmented generation) query."""
|
|
38
|
+
|
|
39
|
+
answer: str
|
|
40
|
+
sources: List[dict] # type: ignore[type-arg]
|
|
41
|
+
confidence: float
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class Skill:
|
|
46
|
+
"""A skill available in the marketplace."""
|
|
47
|
+
|
|
48
|
+
id: str
|
|
49
|
+
name: str
|
|
50
|
+
description: str
|
|
51
|
+
category: str
|
|
52
|
+
price: float
|
|
53
|
+
publisher: str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class SkillResult:
|
|
58
|
+
"""Result of executing a skill."""
|
|
59
|
+
|
|
60
|
+
skill_id: str
|
|
61
|
+
result: str
|
|
62
|
+
tokens_used: int
|
|
63
|
+
duration: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class DreamResult:
|
|
68
|
+
"""Result of a dream consolidation cycle."""
|
|
69
|
+
|
|
70
|
+
memories_analyzed: int
|
|
71
|
+
patterns_found: int
|
|
72
|
+
new_memories_created: int
|
|
73
|
+
dream_summary: str
|
|
74
|
+
new_memories: List[dict] # type: ignore[type-arg]
|
|
75
|
+
duration: int
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memos-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for memos — persistent brain framework for AI agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://memos.io
|
|
7
|
+
Project-URL: Repository, https://github.com/memos/memos-py
|
|
8
|
+
Project-URL: Documentation, https://docs.memos.io
|
|
9
|
+
Requires-Python: <4.0,>=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: httpx>=0.24.0
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
15
|
+
Requires-Dist: pytest-mock>=3.0; extra == "test"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# MEMOS Python SDK
|
|
19
|
+
|
|
20
|
+
Python SDK for [MEMOS](https://memos.io) — persistent brain framework for AI agents.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
```bash
|
|
24
|
+
pip install memos-ai
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Publish Verification
|
|
28
|
+
```bash
|
|
29
|
+
python3 -m venv verify
|
|
30
|
+
source verify/bin/activate
|
|
31
|
+
pip install memos-ai
|
|
32
|
+
python3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from memos import MemosClient
|
|
37
|
+
|
|
38
|
+
print(MemosClient)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Expected output:
|
|
42
|
+
```text
|
|
43
|
+
<class 'memos.client.MemosClient'>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick Start
|
|
47
|
+
```python
|
|
48
|
+
from memos import MemosClient
|
|
49
|
+
|
|
50
|
+
client = MemosClient(
|
|
51
|
+
api_key="your_api_key",
|
|
52
|
+
agent_id="your_agent_id"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Store a memory
|
|
56
|
+
memory = client.store_memory(
|
|
57
|
+
content="User prefers Python over JavaScript",
|
|
58
|
+
type="semantic",
|
|
59
|
+
importance=4
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Search memories
|
|
63
|
+
results = client.search("language preferences")
|
|
64
|
+
for r in results:
|
|
65
|
+
print(f"{r.score:.2f} — {r.content}")
|
|
66
|
+
|
|
67
|
+
# Ask with memory context
|
|
68
|
+
response = client.query("What language does this user prefer?")
|
|
69
|
+
print(response.answer)
|
|
70
|
+
|
|
71
|
+
# Trigger dream consolidation
|
|
72
|
+
dream = client.trigger_dream()
|
|
73
|
+
print(f"Created {dream.new_memories_created} new memories")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Authentication
|
|
77
|
+
Get your API key and agent ID from https://memos.io/dashboard
|
|
78
|
+
|
|
79
|
+
## Methods Reference
|
|
80
|
+
|
|
81
|
+
| Method | Parameters | Returns | Description |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| `store_memory` | `content` (str), `type` (str="episodic"), `importance` (int=3), `tags` (list[str]=None) | `Memory` | Store a new memory for the agent |
|
|
84
|
+
| `list_memories` | None | `list[Memory]` | List all memories for the agent |
|
|
85
|
+
| `delete_memory` | `memory_id` (str) | `bool` | Delete a specific memory by ID |
|
|
86
|
+
| `search` | `query` (str), `search_type` (str="keyword"), `limit` (int=10) | `list[SearchResult]` | Search agent memories |
|
|
87
|
+
| `query` | `question` (str), `include_sources` (bool=True), `conversation_history` (list[dict]=None) | `RAGResponse` | Ask a question using RAG |
|
|
88
|
+
| `trigger_dream` | None | `DreamResult` | Trigger a dream consolidation cycle |
|
|
89
|
+
| `list_skills` | None | `list[Skill]` | List all available skills in the marketplace |
|
|
90
|
+
| `execute_skill` | `skill_id` (str), `input` (str) | `SkillResult` | Execute a skill |
|
|
91
|
+
| `run_pipeline` | `steps` (list[dict]), `input` (str) | `dict` | Run a multi-step pipeline |
|
|
92
|
+
| `get_identity` | None | `dict` | Get the agent's identity and reputation |
|
|
93
|
+
|
|
94
|
+
## Error Handling
|
|
95
|
+
```python
|
|
96
|
+
from memos import MemosError, AuthError, RateLimitError
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
client.store_memory("...")
|
|
100
|
+
except AuthError:
|
|
101
|
+
print("Check your API key at memos.io/profile")
|
|
102
|
+
except RateLimitError:
|
|
103
|
+
print("Rate limit hit — slow down requests")
|
|
104
|
+
except MemosError as e:
|
|
105
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Context Manager
|
|
109
|
+
```python
|
|
110
|
+
with MemosClient(api_key="...", agent_id="...") as client:
|
|
111
|
+
client.store_memory("...")
|
|
112
|
+
# HTTP connection closed automatically
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Requirements
|
|
116
|
+
Python 3.8+
|
|
117
|
+
No additional dependencies beyond `httpx`.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
memos/__init__.py
|
|
6
|
+
memos/client.py
|
|
7
|
+
memos/exceptions.py
|
|
8
|
+
memos/models.py
|
|
9
|
+
memos_ai.egg-info/PKG-INFO
|
|
10
|
+
memos_ai.egg-info/SOURCES.txt
|
|
11
|
+
memos_ai.egg-info/dependency_links.txt
|
|
12
|
+
memos_ai.egg-info/requires.txt
|
|
13
|
+
memos_ai.egg-info/top_level.txt
|
|
14
|
+
tests/test_client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
memos
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "memos-ai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for memos — persistent brain framework for AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.11,<4.0"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"httpx>=0.24.0"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
test = ["pytest>=7.0", "pytest-mock>=3.0"]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://memos.io"
|
|
22
|
+
Repository = "https://github.com/memos/memos-py"
|
|
23
|
+
Documentation = "https://docs.memos.io"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["."]
|
|
27
|
+
include = ["memos*"]
|
memos_ai-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
from memos import MemosClient, AuthError, RateLimitError, ServerError, MemosError
|
|
5
|
+
from memos.models import Memory, SearchResult, RAGResponse, DreamResult
|
|
6
|
+
|
|
7
|
+
def make_mock_response(status_code: int, json_data: dict):
|
|
8
|
+
mock = MagicMock()
|
|
9
|
+
mock.status_code = status_code
|
|
10
|
+
mock.is_success = 200 <= status_code < 300
|
|
11
|
+
mock.json.return_value = json_data
|
|
12
|
+
mock.text = str(json_data)
|
|
13
|
+
return mock
|
|
14
|
+
|
|
15
|
+
def test_init_requires_api_key():
|
|
16
|
+
with pytest.raises(AuthError):
|
|
17
|
+
MemosClient(api_key="", agent_id="agent_123")
|
|
18
|
+
|
|
19
|
+
def test_init_requires_agent_id():
|
|
20
|
+
with pytest.raises(MemosError):
|
|
21
|
+
MemosClient(api_key="mk0s_key", agent_id="")
|
|
22
|
+
|
|
23
|
+
@patch("httpx.Client.post")
|
|
24
|
+
def test_store_memory_success(mock_post):
|
|
25
|
+
mock_post.return_value = make_mock_response(200, {
|
|
26
|
+
"memory": {
|
|
27
|
+
"id": "mem_1",
|
|
28
|
+
"agent_id": "agent_123",
|
|
29
|
+
"content": "test content",
|
|
30
|
+
"type": "episodic",
|
|
31
|
+
"importance": 3
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
35
|
+
result = client.store_memory("test content")
|
|
36
|
+
assert isinstance(result, Memory)
|
|
37
|
+
assert result.content == "test content"
|
|
38
|
+
|
|
39
|
+
@patch("httpx.Client.post")
|
|
40
|
+
def test_store_memory_401(mock_post):
|
|
41
|
+
mock_post.return_value = make_mock_response(401, {"error": "Unauthorized"})
|
|
42
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
43
|
+
with pytest.raises(AuthError):
|
|
44
|
+
client.store_memory("test")
|
|
45
|
+
|
|
46
|
+
@patch("httpx.Client.post")
|
|
47
|
+
def test_store_memory_429(mock_post):
|
|
48
|
+
mock_post.return_value = make_mock_response(429, {"error": "Rate limit"})
|
|
49
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
50
|
+
with pytest.raises(RateLimitError):
|
|
51
|
+
client.store_memory("test")
|
|
52
|
+
|
|
53
|
+
@patch("httpx.Client.post")
|
|
54
|
+
def test_store_memory_500(mock_post):
|
|
55
|
+
mock_post.return_value = make_mock_response(500, {"error": "Server error"})
|
|
56
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
57
|
+
with pytest.raises(ServerError):
|
|
58
|
+
client.store_memory("test")
|
|
59
|
+
|
|
60
|
+
@patch("httpx.Client.get")
|
|
61
|
+
def test_list_memories_returns_list(mock_get):
|
|
62
|
+
mock_get.return_value = make_mock_response(200, {
|
|
63
|
+
"memories": [
|
|
64
|
+
{
|
|
65
|
+
"id": "mem_1",
|
|
66
|
+
"agent_id": "agent",
|
|
67
|
+
"content": "c1",
|
|
68
|
+
"type": "episodic",
|
|
69
|
+
"importance": 3
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
})
|
|
73
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
74
|
+
result = client.list_memories()
|
|
75
|
+
assert isinstance(result, list)
|
|
76
|
+
assert all(isinstance(m, Memory) for m in result)
|
|
77
|
+
|
|
78
|
+
@patch("httpx.Client.post")
|
|
79
|
+
def test_search_returns_results(mock_post):
|
|
80
|
+
mock_post.return_value = make_mock_response(200, {
|
|
81
|
+
"results": [
|
|
82
|
+
{
|
|
83
|
+
"id": "mem_1",
|
|
84
|
+
"content": "test result",
|
|
85
|
+
"type": "episodic",
|
|
86
|
+
"importance": 3,
|
|
87
|
+
"score": 0.95
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
})
|
|
91
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
92
|
+
result = client.search("test query")
|
|
93
|
+
assert isinstance(result, list)
|
|
94
|
+
assert all(isinstance(r, SearchResult) for r in result)
|
|
95
|
+
|
|
96
|
+
@patch("httpx.Client.post")
|
|
97
|
+
def test_query_returns_rag_response(mock_post):
|
|
98
|
+
mock_post.return_value = make_mock_response(200, {
|
|
99
|
+
"answer": "this is the answer",
|
|
100
|
+
"sources": [],
|
|
101
|
+
"confidence": 0.9
|
|
102
|
+
})
|
|
103
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
104
|
+
result = client.query("what do I prefer?")
|
|
105
|
+
assert isinstance(result, RAGResponse)
|
|
106
|
+
assert result.answer == "this is the answer"
|
|
107
|
+
|
|
108
|
+
@patch("httpx.Client.post")
|
|
109
|
+
def test_trigger_dream_returns_dream_result(mock_post):
|
|
110
|
+
mock_post.return_value = make_mock_response(200, {
|
|
111
|
+
"memoriesAnalyzed": 10,
|
|
112
|
+
"patternsFound": 2,
|
|
113
|
+
"newMemoriesCreated": 1,
|
|
114
|
+
"dreamSummary": "summary",
|
|
115
|
+
"newMemories": [],
|
|
116
|
+
"duration": 500
|
|
117
|
+
})
|
|
118
|
+
client = MemosClient(api_key="key", agent_id="agent")
|
|
119
|
+
result = client.trigger_dream()
|
|
120
|
+
assert isinstance(result, DreamResult)
|
|
121
|
+
assert result.memories_analyzed == 10
|
|
122
|
+
|
|
123
|
+
def test_context_manager_closes_client():
|
|
124
|
+
with patch.object(MemosClient, 'close') as mock_close:
|
|
125
|
+
with MemosClient(api_key="key", agent_id="agent"):
|
|
126
|
+
pass
|
|
127
|
+
mock_close.assert_called_once()
|