sovant 1.0.0__py3-none-any.whl → 1.0.3__py3-none-any.whl
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.
- sovant/__init__.py +3 -62
- sovant/client.py +83 -80
- sovant/models.py +30 -0
- sovant-1.0.3.dist-info/METADATA +118 -0
- sovant-1.0.3.dist-info/RECORD +14 -0
- {sovant-1.0.0.dist-info → sovant-1.0.3.dist-info}/licenses/LICENSE +1 -1
- sovant-1.0.0.dist-info/METADATA +0 -492
- sovant-1.0.0.dist-info/RECORD +0 -13
- {sovant-1.0.0.dist-info → sovant-1.0.3.dist-info}/WHEEL +0 -0
- {sovant-1.0.0.dist-info → sovant-1.0.3.dist-info}/top_level.txt +0 -0
sovant/__init__.py
CHANGED
|
@@ -1,63 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
from .client import Sovant, SovantError
|
|
2
|
+
from .models import MemoryCreate, MemoryResult, SearchQuery
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
__version__ = "1.0.0"
|
|
8
|
-
|
|
9
|
-
from .client import SovantClient, AsyncSovantClient
|
|
10
|
-
from .exceptions import (
|
|
11
|
-
SovantError,
|
|
12
|
-
AuthenticationError,
|
|
13
|
-
RateLimitError,
|
|
14
|
-
ValidationError,
|
|
15
|
-
NotFoundError,
|
|
16
|
-
NetworkError,
|
|
17
|
-
)
|
|
18
|
-
from .types import (
|
|
19
|
-
Memory,
|
|
20
|
-
MemoryType,
|
|
21
|
-
EmotionType,
|
|
22
|
-
EmotionalContext,
|
|
23
|
-
CreateMemoryInput,
|
|
24
|
-
UpdateMemoryInput,
|
|
25
|
-
SearchOptions,
|
|
26
|
-
SearchResult,
|
|
27
|
-
Thread,
|
|
28
|
-
ThreadStatus,
|
|
29
|
-
CreateThreadInput,
|
|
30
|
-
UpdateThreadInput,
|
|
31
|
-
ThreadStats,
|
|
32
|
-
BatchCreateResult,
|
|
33
|
-
PaginatedResponse,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
__all__ = [
|
|
37
|
-
# Client classes
|
|
38
|
-
"SovantClient",
|
|
39
|
-
"AsyncSovantClient",
|
|
40
|
-
# Exceptions
|
|
41
|
-
"SovantError",
|
|
42
|
-
"AuthenticationError",
|
|
43
|
-
"RateLimitError",
|
|
44
|
-
"ValidationError",
|
|
45
|
-
"NotFoundError",
|
|
46
|
-
"NetworkError",
|
|
47
|
-
# Types
|
|
48
|
-
"Memory",
|
|
49
|
-
"MemoryType",
|
|
50
|
-
"EmotionType",
|
|
51
|
-
"EmotionalContext",
|
|
52
|
-
"CreateMemoryInput",
|
|
53
|
-
"UpdateMemoryInput",
|
|
54
|
-
"SearchOptions",
|
|
55
|
-
"SearchResult",
|
|
56
|
-
"Thread",
|
|
57
|
-
"ThreadStatus",
|
|
58
|
-
"CreateThreadInput",
|
|
59
|
-
"UpdateThreadInput",
|
|
60
|
-
"ThreadStats",
|
|
61
|
-
"BatchCreateResult",
|
|
62
|
-
"PaginatedResponse",
|
|
63
|
-
]
|
|
4
|
+
__version__ = "0.1.0"
|
sovant/client.py
CHANGED
|
@@ -1,87 +1,90 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import httpx
|
|
4
|
+
from typing import Any, Dict
|
|
5
|
+
from .models import MemoryCreate, SearchQuery
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
class SovantError(Exception):
|
|
8
|
+
def __init__(self, message: str, code: str, status: int | None = None, details: Any | None = None):
|
|
9
|
+
super().__init__(message)
|
|
10
|
+
self.code = code
|
|
11
|
+
self.status = status
|
|
12
|
+
self.details = details
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
class Sovant:
|
|
15
|
+
def __init__(self, api_key: str | None = None, base_url: str | None = None, timeout: float = 30.0):
|
|
16
|
+
self.api_key = api_key or os.getenv("SOVANT_API_KEY")
|
|
17
|
+
if not self.api_key:
|
|
18
|
+
raise ValueError("Missing api_key")
|
|
19
|
+
self.base_url = (base_url or os.getenv("SOVANT_BASE_URL") or "https://api.sovant.ai").rstrip("/")
|
|
20
|
+
self.timeout = timeout
|
|
21
|
+
self._client = httpx.Client(
|
|
22
|
+
timeout=self.timeout,
|
|
23
|
+
headers={
|
|
24
|
+
"authorization": f"Bearer {self.api_key}",
|
|
25
|
+
"content-type": "application/json"
|
|
26
|
+
}
|
|
27
|
+
)
|
|
8
28
|
|
|
29
|
+
def _handle(self, r: httpx.Response):
|
|
30
|
+
if r.status_code >= 400:
|
|
31
|
+
try:
|
|
32
|
+
body = r.json()
|
|
33
|
+
except Exception:
|
|
34
|
+
body = {"message": r.text}
|
|
35
|
+
msg = body.get("message") or r.reason_phrase
|
|
36
|
+
code = body.get("code") or f"HTTP_{r.status_code}"
|
|
37
|
+
raise SovantError(msg, code, r.status_code, body)
|
|
38
|
+
if not r.text:
|
|
39
|
+
return None
|
|
40
|
+
try:
|
|
41
|
+
return r.json()
|
|
42
|
+
except Exception:
|
|
43
|
+
return r.text
|
|
9
44
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Initialize the Sovant client.
|
|
45
|
+
def memory_create(self, create: MemoryCreate):
|
|
46
|
+
# Convert data field to content field for API
|
|
47
|
+
body = create.model_dump()
|
|
48
|
+
if 'data' in body:
|
|
49
|
+
body['content'] = json.dumps(body.pop('data')) if not isinstance(body.get('data'), str) else body.pop('data')
|
|
16
50
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if isinstance(api_key, str):
|
|
25
|
-
config = Config(api_key=api_key)
|
|
26
|
-
else:
|
|
27
|
-
config = api_key
|
|
28
|
-
|
|
29
|
-
self.memories = Memories(config)
|
|
30
|
-
self.threads = Threads(config)
|
|
31
|
-
self._config = config
|
|
32
|
-
|
|
33
|
-
def ping(self) -> Dict[str, str]:
|
|
34
|
-
"""Test API connection."""
|
|
35
|
-
client = BaseClient(self._config)
|
|
36
|
-
return client.request("GET", "/health")
|
|
37
|
-
|
|
38
|
-
def get_usage(self) -> Dict[str, Any]:
|
|
39
|
-
"""Get current API usage and quota."""
|
|
40
|
-
client = BaseClient(self._config)
|
|
41
|
-
return client.request("GET", "/usage")
|
|
51
|
+
# Ensure type has a default
|
|
52
|
+
if 'type' not in body or body['type'] is None:
|
|
53
|
+
body['type'] = 'journal'
|
|
54
|
+
|
|
55
|
+
r = self._client.post(f"{self.base_url}/v1/memory", content=json.dumps(body))
|
|
56
|
+
return self._handle(r)
|
|
42
57
|
|
|
58
|
+
def memory_get(self, id: str):
|
|
59
|
+
r = self._client.get(f"{self.base_url}/v1/memories/{id}")
|
|
60
|
+
return self._handle(r)
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"""Get current API usage and quota."""
|
|
74
|
-
async with AsyncBaseClient(self._config) as client:
|
|
75
|
-
return await client.request("GET", "/usage")
|
|
76
|
-
|
|
77
|
-
async def __aenter__(self):
|
|
78
|
-
"""Context manager entry."""
|
|
79
|
-
return self
|
|
80
|
-
|
|
81
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
82
|
-
"""Context manager exit."""
|
|
83
|
-
# Clean up any open connections
|
|
84
|
-
if hasattr(self.memories, "client"):
|
|
85
|
-
await self.memories.client.aclose()
|
|
86
|
-
if hasattr(self.threads, "client"):
|
|
87
|
-
await self.threads.client.aclose()
|
|
62
|
+
def memory_search(self, q: SearchQuery):
|
|
63
|
+
params = {}
|
|
64
|
+
if q.query:
|
|
65
|
+
params['query'] = q.query
|
|
66
|
+
if q.type:
|
|
67
|
+
params['type'] = q.type
|
|
68
|
+
if q.tags:
|
|
69
|
+
params['tags'] = ','.join(q.tags)
|
|
70
|
+
if q.thread_id:
|
|
71
|
+
params['thread_id'] = q.thread_id
|
|
72
|
+
if q.limit:
|
|
73
|
+
params['limit'] = str(q.limit)
|
|
74
|
+
if q.from_date:
|
|
75
|
+
params['from_date'] = q.from_date
|
|
76
|
+
if q.to_date:
|
|
77
|
+
params['to_date'] = q.to_date
|
|
78
|
+
r = self._client.get(f"{self.base_url}/v1/memory/search", params=params)
|
|
79
|
+
return self._handle(r)
|
|
80
|
+
|
|
81
|
+
def memory_update(self, id: str, patch: Dict[str, Any]):
|
|
82
|
+
# Convert data field to content field if present
|
|
83
|
+
if 'data' in patch:
|
|
84
|
+
patch['content'] = json.dumps(patch.pop('data')) if not isinstance(patch.get('data'), str) else patch.pop('data')
|
|
85
|
+
r = self._client.patch(f"{self.base_url}/v1/memories/{id}", content=json.dumps(patch))
|
|
86
|
+
return self._handle(r)
|
|
87
|
+
|
|
88
|
+
def memory_delete(self, id: str):
|
|
89
|
+
r = self._client.delete(f"{self.base_url}/v1/memories/{id}")
|
|
90
|
+
return self._handle(r)
|
sovant/models.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from typing import Any, Dict, List, Optional, Literal
|
|
3
|
+
|
|
4
|
+
class MemoryCreate(BaseModel):
|
|
5
|
+
data: Any
|
|
6
|
+
type: Optional[Literal['journal', 'insight', 'observation', 'task', 'preference']] = 'journal'
|
|
7
|
+
ttl: Optional[str] = None
|
|
8
|
+
tags: Optional[List[str]] = None
|
|
9
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
10
|
+
thread_id: Optional[str] = None
|
|
11
|
+
|
|
12
|
+
class MemoryResult(BaseModel):
|
|
13
|
+
id: str
|
|
14
|
+
content: str
|
|
15
|
+
type: str
|
|
16
|
+
ttl: Optional[str] = None
|
|
17
|
+
tags: Optional[List[str]] = None
|
|
18
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
19
|
+
thread_id: Optional[str] = None
|
|
20
|
+
created_at: Optional[str] = None
|
|
21
|
+
updated_at: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
class SearchQuery(BaseModel):
|
|
24
|
+
query: Optional[str] = None
|
|
25
|
+
type: Optional[str] = None
|
|
26
|
+
tags: Optional[List[str]] = None
|
|
27
|
+
thread_id: Optional[str] = None
|
|
28
|
+
limit: Optional[int] = Field(default=10, ge=1, le=100)
|
|
29
|
+
from_date: Optional[str] = None
|
|
30
|
+
to_date: Optional[str] = None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sovant
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Sovant Memory-as-a-Service Python SDK
|
|
5
|
+
Author: Sovant
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: httpx>=0.27.0
|
|
11
|
+
Requires-Dist: pydantic>=2.8.2
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# Sovant Python SDK
|
|
15
|
+
|
|
16
|
+
**AI Infrastructure: Memory-as-a-Service for AI systems.**
|
|
17
|
+
The official Python client for the Sovant Memory API.
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/sovant/)
|
|
20
|
+
[](https://sovant.ai/docs)
|
|
21
|
+
[](https://github.com/sovant-ai/sovant)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What is Sovant?
|
|
26
|
+
|
|
27
|
+
Sovant is an **AI infrastructure company** providing the **memory layer for AI systems**.
|
|
28
|
+
Our platform makes it simple to persist, search, and manage conversational/context data securely across sessions, channels, and applications.
|
|
29
|
+
|
|
30
|
+
- **Problem:** Most AI systems forget once a session ends → compliance risk, knowledge loss, poor UX.
|
|
31
|
+
- **Solution:** Sovant provides a **Memory-as-a-Service API** — store, retrieve, and search memories with audit-ready controls.
|
|
32
|
+
- **Built for:** Developers & enterprises building AI agents, copilots, or compliance-driven apps that need persistent context.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install sovant
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 60-Second Quickstart
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from sovant import Sovant, MemoryCreate, SearchQuery
|
|
46
|
+
|
|
47
|
+
client = Sovant(api_key="YOUR_API_KEY")
|
|
48
|
+
|
|
49
|
+
# Create a memory
|
|
50
|
+
created = client.memory_create(MemoryCreate(
|
|
51
|
+
content="User prefers morning meetings",
|
|
52
|
+
type="preference",
|
|
53
|
+
tags=["customer:123"],
|
|
54
|
+
# metadata={"source": "crm"},
|
|
55
|
+
# thread_id="00000000-0000-0000-0000-000000000000",
|
|
56
|
+
))
|
|
57
|
+
print(created)
|
|
58
|
+
|
|
59
|
+
# Search memories
|
|
60
|
+
results = client.memory_search(SearchQuery(
|
|
61
|
+
query="meeting preferences",
|
|
62
|
+
topK=3,
|
|
63
|
+
# tags=["customer:123"],
|
|
64
|
+
# thread_id="...",
|
|
65
|
+
))
|
|
66
|
+
print(results)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
- **Memory CRUD** — create, retrieve, update (PATCH/PUT), delete
|
|
72
|
+
- **Semantic Search** — full-text/semantic with topK and filters
|
|
73
|
+
- **Threads** — group related memories via thread_id (REST API)
|
|
74
|
+
- **Batch Operations (Beta)** — atomic multi-operation requests
|
|
75
|
+
- **Compliance-ready** — audit trails, PDPA/GDPR-friendly design
|
|
76
|
+
- **SDKs** — official Python & TypeScript clients + REST reference
|
|
77
|
+
|
|
78
|
+
## SDKs & Documentation
|
|
79
|
+
|
|
80
|
+
- **Python** (this package) on PyPI: https://pypi.org/project/sovant/
|
|
81
|
+
- **TypeScript** on npm: https://www.npmjs.com/package/@sovant/sdk
|
|
82
|
+
- **REST API Reference**: https://sovant.ai/docs/api
|
|
83
|
+
- **Full Documentation**: https://sovant.ai/docs
|
|
84
|
+
|
|
85
|
+
## API Overview
|
|
86
|
+
|
|
87
|
+
### Endpoints Summary
|
|
88
|
+
|
|
89
|
+
- `POST /api/v1/memory` — Create memory
|
|
90
|
+
- `GET /api/v1/memory` — List memories
|
|
91
|
+
- `GET /api/v1/memories/{id}` — Get by ID
|
|
92
|
+
- `PATCH | PUT /api/v1/memories/{id}` — Update
|
|
93
|
+
- `DELETE /api/v1/memories/{id}` — Delete
|
|
94
|
+
- `GET /api/v1/memory/search` — Search
|
|
95
|
+
- `POST /api/v1/memory/batch` — Batch (Beta)
|
|
96
|
+
|
|
97
|
+
**Auth:** Bearer token in the `Authorization` header.
|
|
98
|
+
|
|
99
|
+
## Field Mapping Note
|
|
100
|
+
|
|
101
|
+
- **API expects:** `content` (required), optional `type`, `tags`, `metadata`, `thread_id`.
|
|
102
|
+
- **SDK convenience:** Some client methods may accept `data` and map it to `content` internally.
|
|
103
|
+
- **Deprecated:** `subject` is ignored by the API. Use `thread_id`, `tags`, or `metadata` for grouping.
|
|
104
|
+
|
|
105
|
+
## Status of Advanced Features
|
|
106
|
+
|
|
107
|
+
- **Batch API:** Available, marked Beta (contract may evolve).
|
|
108
|
+
- **Threads:** Fully supported via REST API.
|
|
109
|
+
- **Webhooks / Personalization / Multi-Channel Sync:** Coming soon.
|
|
110
|
+
|
|
111
|
+
## Versioning & Changelog
|
|
112
|
+
|
|
113
|
+
- **Stable baseline:** 1.0.3
|
|
114
|
+
- See [CHANGELOG.md](https://github.com/sovant-ai/sovant) in the repo for details.
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT © Sovant AI
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
sovant/__init__.py,sha256=1GYQzmb1Ni5-y874HOs1J-rYrpx-iBzR4_cCEBFIjpk,122
|
|
2
|
+
sovant/base_client.py,sha256=Vmn6OGywGwLbH5cEeflSjVOFwn5iX_YdfTdUq9pWWxA,8778
|
|
3
|
+
sovant/client.py,sha256=QmcUnK_IFXIE9VbvNy3VeI8rsGeeJpLkUvB-721VNBY,3356
|
|
4
|
+
sovant/exceptions.py,sha256=MQMSgk7ckXnAbe7hPPpbKOnRBHSPxuCY7WSjqfJAvd0,1557
|
|
5
|
+
sovant/models.py,sha256=avDAITMptDDdDfNH_ed854Q7kF6z_1OzjwJ9Xeft_-8,979
|
|
6
|
+
sovant/types.py,sha256=gnvdXksJt8LObti7nc6eHSBCB7Pz7SNpS5o_HRTq_kA,6098
|
|
7
|
+
sovant/resources/__init__.py,sha256=cPFIM7h8duviDdeHudnVEAmv3F89RHQxdH5BCRWFteQ,193
|
|
8
|
+
sovant/resources/memories.py,sha256=bKKE0uWqFkPa1OEvbK1LrdSD7v6N04RhJ_2VDoPPQBA,11379
|
|
9
|
+
sovant/resources/threads.py,sha256=mN29xP0JODmZBKyfhpeqJyViUWNVMAx3TlYAW-1ruTs,12558
|
|
10
|
+
sovant-1.0.3.dist-info/licenses/LICENSE,sha256=rnNP6-elrMIlQ9jf2aqnHZMyyQ_wvF3y1hTpVUusCWU,1062
|
|
11
|
+
sovant-1.0.3.dist-info/METADATA,sha256=ul2TZUpPijkKMc0ySZn7goqy_5VDzFuj-y7KzZI5zew,3757
|
|
12
|
+
sovant-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
sovant-1.0.3.dist-info/top_level.txt,sha256=za6eVEsYd_ZQQs8vrmEWNcAR58r1wCDge_jA60e4CvQ,7
|
|
14
|
+
sovant-1.0.3.dist-info/RECORD,,
|
sovant-1.0.0.dist-info/METADATA
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: sovant
|
|
3
|
-
Version: 1.0.0
|
|
4
|
-
Summary: Official Python SDK for Sovant Memory API
|
|
5
|
-
Author-email: Sovant AI <support@sovant.ai>
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2024 Sovant AI
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
Project-URL: Homepage, https://sovant.ai
|
|
28
|
-
Project-URL: Documentation, https://docs.sovant.ai
|
|
29
|
-
Project-URL: Repository, https://github.com/sovant-ai/python-sdk
|
|
30
|
-
Project-URL: Issues, https://github.com/sovant-ai/python-sdk/issues
|
|
31
|
-
Keywords: sovant,memory,api,ai,sdk
|
|
32
|
-
Classifier: Development Status :: 4 - Beta
|
|
33
|
-
Classifier: Intended Audience :: Developers
|
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
-
Classifier: Programming Language :: Python :: 3
|
|
36
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
-
Requires-Python: >=3.8
|
|
43
|
-
Description-Content-Type: text/markdown
|
|
44
|
-
License-File: LICENSE
|
|
45
|
-
Requires-Dist: httpx>=0.25.0
|
|
46
|
-
Requires-Dist: pydantic>=2.0.0
|
|
47
|
-
Requires-Dist: python-dateutil>=2.8.0
|
|
48
|
-
Provides-Extra: async
|
|
49
|
-
Requires-Dist: httpx[http2]; extra == "async"
|
|
50
|
-
Provides-Extra: dev
|
|
51
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
52
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
53
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
54
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
55
|
-
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
56
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
57
|
-
Dynamic: license-file
|
|
58
|
-
|
|
59
|
-
# Sovant Python SDK
|
|
60
|
-
|
|
61
|
-
Official Python SDK for the Sovant Memory API. Build AI applications with persistent memory, semantic search, and intelligent context management.
|
|
62
|
-
|
|
63
|
-
> **Note: Coming Soon!** This SDK is currently in development and will be available on PyPI soon. In the meantime, you can use our REST API directly.
|
|
64
|
-
|
|
65
|
-
## Installation
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
# Coming soon
|
|
69
|
-
pip install sovant
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
For async support:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
# Coming soon
|
|
76
|
-
pip install sovant[async]
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Quick Start
|
|
80
|
-
|
|
81
|
-
```python
|
|
82
|
-
from sovant import SovantClient
|
|
83
|
-
|
|
84
|
-
# Initialize the client
|
|
85
|
-
client = SovantClient(api_key="YOUR_API_KEY")
|
|
86
|
-
|
|
87
|
-
# Create a memory
|
|
88
|
-
memory = client.memories.create({
|
|
89
|
-
"content": "User prefers Python for data science projects",
|
|
90
|
-
"type": "preference",
|
|
91
|
-
"metadata": {
|
|
92
|
-
"confidence": 0.95,
|
|
93
|
-
"context": "programming_languages"
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
print(f"Created memory: {memory.id}")
|
|
98
|
-
|
|
99
|
-
# Search memories
|
|
100
|
-
results = client.memories.search({
|
|
101
|
-
"query": "What programming languages does the user prefer?",
|
|
102
|
-
"limit": 5
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
for result in results:
|
|
106
|
-
print(f"- {result.content} (relevance: {result.relevance_score:.2f})")
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Features
|
|
110
|
-
|
|
111
|
-
- 🐍 **Full Type Hints** - Complete type annotations for better IDE support
|
|
112
|
-
- 🔄 **Async/Await Support** - Built for modern Python applications
|
|
113
|
-
- 🔁 **Automatic Retries** - Configurable retry logic with exponential backoff
|
|
114
|
-
- 📦 **Batch Operations** - Efficient bulk create/delete operations
|
|
115
|
-
- 🎯 **Smart Search** - Semantic, keyword, and hybrid search modes
|
|
116
|
-
- 🧵 **Thread Management** - Organize memories into contextual threads
|
|
117
|
-
- 📊 **Analytics** - Built-in insights and statistics
|
|
118
|
-
- 🛡️ **Comprehensive Error Handling** - Typed exceptions for all error cases
|
|
119
|
-
|
|
120
|
-
## Configuration
|
|
121
|
-
|
|
122
|
-
```python
|
|
123
|
-
from sovant import SovantClient, Config
|
|
124
|
-
|
|
125
|
-
# Using configuration object
|
|
126
|
-
config = Config(
|
|
127
|
-
api_key="YOUR_API_KEY",
|
|
128
|
-
base_url="https://api.sovant.ai/v1", # Optional
|
|
129
|
-
timeout=30, # Request timeout in seconds
|
|
130
|
-
max_retries=3, # Number of retries for failed requests
|
|
131
|
-
retry_delay=1.0 # Initial delay between retries
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
client = SovantClient(config)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
You can also use environment variables:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
export SOVANT_API_KEY="YOUR_API_KEY"
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
```python
|
|
144
|
-
from sovant import SovantClient
|
|
145
|
-
|
|
146
|
-
# Automatically reads from SOVANT_API_KEY env var
|
|
147
|
-
client = SovantClient()
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Memory Operations
|
|
151
|
-
|
|
152
|
-
### Create a Memory
|
|
153
|
-
|
|
154
|
-
```python
|
|
155
|
-
from sovant import MemoryType, EmotionType
|
|
156
|
-
|
|
157
|
-
memory = client.memories.create({
|
|
158
|
-
"content": "User completed the onboarding tutorial",
|
|
159
|
-
"type": MemoryType.EVENT,
|
|
160
|
-
"importance": 0.8,
|
|
161
|
-
"metadata": {
|
|
162
|
-
"step_completed": "tutorial",
|
|
163
|
-
"duration_seconds": 180
|
|
164
|
-
},
|
|
165
|
-
"tags": ["onboarding", "milestone"],
|
|
166
|
-
"emotion": {
|
|
167
|
-
"type": EmotionType.POSITIVE,
|
|
168
|
-
"intensity": 0.7
|
|
169
|
-
},
|
|
170
|
-
"action_items": ["Send welcome email", "Unlock advanced features"]
|
|
171
|
-
})
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Update a Memory
|
|
175
|
-
|
|
176
|
-
```python
|
|
177
|
-
updated = client.memories.update(memory.id, {
|
|
178
|
-
"importance": 0.9,
|
|
179
|
-
"follow_up_required": True,
|
|
180
|
-
"follow_up_due": "2024-12-31T23:59:59Z"
|
|
181
|
-
})
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Search Memories
|
|
185
|
-
|
|
186
|
-
```python
|
|
187
|
-
# Semantic search
|
|
188
|
-
semantic_results = client.memories.search({
|
|
189
|
-
"query": "user achievements and milestones",
|
|
190
|
-
"search_type": "semantic",
|
|
191
|
-
"limit": 10
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
# Filtered search
|
|
195
|
-
from sovant import MemoryType
|
|
196
|
-
|
|
197
|
-
filtered_results = client.memories.search({
|
|
198
|
-
"query": "preferences",
|
|
199
|
-
"type": [MemoryType.PREFERENCE, MemoryType.DECISION],
|
|
200
|
-
"tags": ["important"],
|
|
201
|
-
"created_after": "2024-01-01",
|
|
202
|
-
"sort_by": "importance",
|
|
203
|
-
"sort_order": "desc"
|
|
204
|
-
})
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Batch Operations
|
|
208
|
-
|
|
209
|
-
```python
|
|
210
|
-
# Batch create
|
|
211
|
-
memories_data = [
|
|
212
|
-
{"content": "User is a data scientist", "type": "observation"},
|
|
213
|
-
{"content": "User works with ML models", "type": "learning"},
|
|
214
|
-
{"content": "User prefers Jupyter notebooks", "type": "preference"}
|
|
215
|
-
]
|
|
216
|
-
|
|
217
|
-
batch_result = client.memories.create_batch(memories_data)
|
|
218
|
-
print(f"Created {batch_result.success_count} memories")
|
|
219
|
-
print(f"Failed {batch_result.failed_count} memories")
|
|
220
|
-
|
|
221
|
-
# Batch delete
|
|
222
|
-
result = client.memories.delete_batch(["mem_123", "mem_456", "mem_789"])
|
|
223
|
-
print(f"Deleted {result['deleted']} memories")
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
## Thread Management
|
|
227
|
-
|
|
228
|
-
### Create a Thread
|
|
229
|
-
|
|
230
|
-
```python
|
|
231
|
-
thread = client.threads.create({
|
|
232
|
-
"name": "Customer Support Chat",
|
|
233
|
-
"description": "Tracking customer issues and resolutions",
|
|
234
|
-
"tags": ["support", "customer", "priority"]
|
|
235
|
-
})
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### Add Memories to Thread
|
|
239
|
-
|
|
240
|
-
```python
|
|
241
|
-
# Add existing memories
|
|
242
|
-
client.threads.add_memories(thread.id, [memory1.id, memory2.id])
|
|
243
|
-
|
|
244
|
-
# Create and add in one operation
|
|
245
|
-
new_memory = client.memories.create({
|
|
246
|
-
"content": "Customer reported login issue",
|
|
247
|
-
"type": "conversation",
|
|
248
|
-
"thread_ids": [thread.id]
|
|
249
|
-
})
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Get Thread with Memories
|
|
253
|
-
|
|
254
|
-
```python
|
|
255
|
-
# Get thread with all memories included
|
|
256
|
-
thread_with_memories = client.threads.get(thread.id, include_memories=True)
|
|
257
|
-
|
|
258
|
-
print(f"Thread: {thread_with_memories.name}")
|
|
259
|
-
print(f"Total memories: {len(thread_with_memories.memories)}")
|
|
260
|
-
|
|
261
|
-
for memory in thread_with_memories.memories:
|
|
262
|
-
print(f"- {memory.content}")
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
### Thread Analytics
|
|
266
|
-
|
|
267
|
-
```python
|
|
268
|
-
stats = client.threads.get_stats(thread.id)
|
|
269
|
-
|
|
270
|
-
print(f"Total memories: {stats.memory_count}")
|
|
271
|
-
print(f"Average importance: {stats.avg_importance:.2f}")
|
|
272
|
-
print(f"Decisions made: {stats.total_decisions}")
|
|
273
|
-
print(f"Open questions: {stats.total_questions}")
|
|
274
|
-
print(f"Action items: {stats.total_action_items}")
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Async Support
|
|
278
|
-
|
|
279
|
-
```python
|
|
280
|
-
import asyncio
|
|
281
|
-
from sovant import AsyncSovantClient
|
|
282
|
-
|
|
283
|
-
async def main():
|
|
284
|
-
# Use async client for better performance
|
|
285
|
-
async with AsyncSovantClient(api_key="YOUR_API_KEY") as client:
|
|
286
|
-
# Create multiple memories concurrently
|
|
287
|
-
memories = await asyncio.gather(
|
|
288
|
-
client.memories.create({"content": "Memory 1"}),
|
|
289
|
-
client.memories.create({"content": "Memory 2"}),
|
|
290
|
-
client.memories.create({"content": "Memory 3"})
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
print(f"Created {len(memories)} memories")
|
|
294
|
-
|
|
295
|
-
# Async search
|
|
296
|
-
results = await client.memories.search({
|
|
297
|
-
"query": "user preferences",
|
|
298
|
-
"search_type": "semantic"
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
for result in results:
|
|
302
|
-
print(f"- {result.content}")
|
|
303
|
-
|
|
304
|
-
asyncio.run(main())
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
## Error Handling
|
|
308
|
-
|
|
309
|
-
The SDK provides typed exceptions for different error scenarios:
|
|
310
|
-
|
|
311
|
-
```python
|
|
312
|
-
from sovant import (
|
|
313
|
-
AuthenticationError,
|
|
314
|
-
RateLimitError,
|
|
315
|
-
ValidationError,
|
|
316
|
-
NotFoundError,
|
|
317
|
-
NetworkError
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
try:
|
|
321
|
-
memory = client.memories.get("invalid_id")
|
|
322
|
-
except NotFoundError:
|
|
323
|
-
print("Memory not found")
|
|
324
|
-
except RateLimitError as e:
|
|
325
|
-
print(f"Rate limited. Retry after {e.retry_after} seconds")
|
|
326
|
-
except ValidationError as e:
|
|
327
|
-
print(f"Validation errors: {e.errors}")
|
|
328
|
-
except AuthenticationError:
|
|
329
|
-
print("Invalid API key")
|
|
330
|
-
except NetworkError:
|
|
331
|
-
print("Network error occurred")
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
## Advanced Usage
|
|
335
|
-
|
|
336
|
-
### Memory Insights
|
|
337
|
-
|
|
338
|
-
```python
|
|
339
|
-
insights = client.memories.get_insights(
|
|
340
|
-
time_range="last_30_days",
|
|
341
|
-
group_by="type"
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
print(f"Total memories: {insights['total_count']}")
|
|
345
|
-
print(f"Type distribution: {insights['type_distribution']}")
|
|
346
|
-
print(f"Emotion distribution: {insights['emotion_distribution']}")
|
|
347
|
-
print(f"Average importance: {insights['importance_stats']['avg']}")
|
|
348
|
-
print(f"Growth rate: {insights['growth_rate']}%")
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
### Related Memories
|
|
352
|
-
|
|
353
|
-
```python
|
|
354
|
-
# Find memories related to a specific memory
|
|
355
|
-
related = client.memories.get_related(
|
|
356
|
-
memory_id=memory.id,
|
|
357
|
-
limit=5,
|
|
358
|
-
threshold=0.7 # Minimum similarity score
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
for r in related:
|
|
362
|
-
print(f"- {r.content} (similarity: {r.relevance_score:.2f})")
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### Thread Operations
|
|
366
|
-
|
|
367
|
-
```python
|
|
368
|
-
# Archive a thread
|
|
369
|
-
archived = client.threads.archive(thread.id)
|
|
370
|
-
|
|
371
|
-
# Search threads
|
|
372
|
-
threads = client.threads.search(
|
|
373
|
-
query="customer support",
|
|
374
|
-
status="active",
|
|
375
|
-
tags=["priority"]
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
# Merge threads
|
|
379
|
-
merged = client.threads.merge(
|
|
380
|
-
target_id=main_thread.id,
|
|
381
|
-
source_ids=[thread2.id, thread3.id]
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
# Clone a thread
|
|
385
|
-
cloned = client.threads.clone(
|
|
386
|
-
thread_id=thread.id,
|
|
387
|
-
name="Cloned Thread",
|
|
388
|
-
include_memories=True
|
|
389
|
-
)
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Pagination
|
|
393
|
-
|
|
394
|
-
```python
|
|
395
|
-
# List memories with pagination
|
|
396
|
-
page1 = client.memories.list(limit=20, offset=0)
|
|
397
|
-
print(f"Total memories: {page1.total}")
|
|
398
|
-
print(f"Has more: {page1.has_more}")
|
|
399
|
-
|
|
400
|
-
# Get next page
|
|
401
|
-
if page1.has_more:
|
|
402
|
-
page2 = client.memories.list(limit=20, offset=20)
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
## Type Safety
|
|
406
|
-
|
|
407
|
-
The SDK uses Pydantic for data validation and provides enums for better type safety:
|
|
408
|
-
|
|
409
|
-
```python
|
|
410
|
-
from sovant import MemoryType, EmotionType, ThreadStatus
|
|
411
|
-
|
|
412
|
-
# Using enums ensures valid values
|
|
413
|
-
memory = client.memories.create({
|
|
414
|
-
"content": "Important decision made",
|
|
415
|
-
"type": MemoryType.DECISION, # Type-safe enum
|
|
416
|
-
"emotion": {
|
|
417
|
-
"type": EmotionType.NEUTRAL, # Type-safe enum
|
|
418
|
-
"intensity": 0.5
|
|
419
|
-
}
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
# IDE will provide autocompletion for all fields
|
|
423
|
-
thread = client.threads.create({
|
|
424
|
-
"name": "Project Planning",
|
|
425
|
-
"status": ThreadStatus.ACTIVE # Type-safe enum
|
|
426
|
-
})
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
## Best Practices
|
|
430
|
-
|
|
431
|
-
1. **Use batch operations for bulk actions**
|
|
432
|
-
|
|
433
|
-
```python
|
|
434
|
-
# Good - single API call
|
|
435
|
-
batch_result = client.memories.create_batch(memories_list)
|
|
436
|
-
|
|
437
|
-
# Avoid - multiple API calls
|
|
438
|
-
for memory_data in memories_list:
|
|
439
|
-
client.memories.create(memory_data)
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
2. **Use async client for concurrent operations**
|
|
443
|
-
|
|
444
|
-
```python
|
|
445
|
-
async with AsyncSovantClient() as client:
|
|
446
|
-
results = await asyncio.gather(*tasks)
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
3. **Handle errors gracefully**
|
|
450
|
-
|
|
451
|
-
```python
|
|
452
|
-
try:
|
|
453
|
-
result = client.memories.search({"query": "test"})
|
|
454
|
-
except RateLimitError as e:
|
|
455
|
-
await asyncio.sleep(e.retry_after)
|
|
456
|
-
# Retry the request
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
4. **Use type hints for better IDE support**
|
|
460
|
-
|
|
461
|
-
```python
|
|
462
|
-
from sovant import Memory, SearchResult
|
|
463
|
-
|
|
464
|
-
def process_memory(memory: Memory) -> None:
|
|
465
|
-
print(f"Processing {memory.id}")
|
|
466
|
-
|
|
467
|
-
def handle_results(results: list[SearchResult]) -> None:
|
|
468
|
-
for result in results:
|
|
469
|
-
print(f"Score: {result.relevance_score}")
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
## Examples
|
|
473
|
-
|
|
474
|
-
Check out the [examples directory](https://github.com/sovant-ai/python-sdk/tree/main/examples) for complete working examples:
|
|
475
|
-
|
|
476
|
-
- Basic CRUD operations
|
|
477
|
-
- Advanced search techniques
|
|
478
|
-
- Thread management workflows
|
|
479
|
-
- Async patterns
|
|
480
|
-
- Error handling
|
|
481
|
-
- Data analysis with pandas integration
|
|
482
|
-
|
|
483
|
-
## Support
|
|
484
|
-
|
|
485
|
-
- 📚 [Documentation](https://docs.sovant.ai)
|
|
486
|
-
- 💬 [Discord Community](https://discord.gg/sovant)
|
|
487
|
-
- 🐛 [Issue Tracker](https://github.com/sovant-ai/python-sdk/issues)
|
|
488
|
-
- 📧 [Email Support](mailto:support@sovant.ai)
|
|
489
|
-
|
|
490
|
-
## License
|
|
491
|
-
|
|
492
|
-
MIT © Sovant AI
|
sovant-1.0.0.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
sovant/__init__.py,sha256=1MoHw4tAMuMjBUakVz6qXqFgKf37ijZVveSOB6wcXwE,1174
|
|
2
|
-
sovant/base_client.py,sha256=Vmn6OGywGwLbH5cEeflSjVOFwn5iX_YdfTdUq9pWWxA,8778
|
|
3
|
-
sovant/client.py,sha256=C71f9tFfbpQAkw5yjOfalNeCPwOqu9M5FyNzuittOAU,2763
|
|
4
|
-
sovant/exceptions.py,sha256=MQMSgk7ckXnAbe7hPPpbKOnRBHSPxuCY7WSjqfJAvd0,1557
|
|
5
|
-
sovant/types.py,sha256=gnvdXksJt8LObti7nc6eHSBCB7Pz7SNpS5o_HRTq_kA,6098
|
|
6
|
-
sovant/resources/__init__.py,sha256=cPFIM7h8duviDdeHudnVEAmv3F89RHQxdH5BCRWFteQ,193
|
|
7
|
-
sovant/resources/memories.py,sha256=bKKE0uWqFkPa1OEvbK1LrdSD7v6N04RhJ_2VDoPPQBA,11379
|
|
8
|
-
sovant/resources/threads.py,sha256=mN29xP0JODmZBKyfhpeqJyViUWNVMAx3TlYAW-1ruTs,12558
|
|
9
|
-
sovant-1.0.0.dist-info/licenses/LICENSE,sha256=Z_4cIqcf0pj97wNZqUXZgynU_gwhNzriM5u3HzlzFzM,1065
|
|
10
|
-
sovant-1.0.0.dist-info/METADATA,sha256=jOe4BqtU1QYbscc59vLCl1d-76wo9wCfCltyyS__GjE,13179
|
|
11
|
-
sovant-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
-
sovant-1.0.0.dist-info/top_level.txt,sha256=za6eVEsYd_ZQQs8vrmEWNcAR58r1wCDge_jA60e4CvQ,7
|
|
13
|
-
sovant-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|