sovant 1.2.0__py3-none-any.whl → 1.3.1__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  from .client import Sovant, SovantError
2
2
  from .models import MemoryCreate, MemoryResult, SearchQuery
3
3
 
4
- __version__ = "0.1.0"
4
+ __version__ = "1.3.1"
sovant/client.py CHANGED
@@ -131,7 +131,7 @@ class Sovant:
131
131
 
132
132
  def memory_create(self, create: MemoryCreate):
133
133
  # Convert data field to content field for API
134
- body = create.model_dump()
134
+ body = create.model_dump(exclude_none=True)
135
135
  if 'data' in body:
136
136
  body['content'] = json.dumps(body.pop('data')) if not isinstance(body.get('data'), str) else body.pop('data')
137
137
 
@@ -217,4 +217,108 @@ class Sovant:
217
217
  "data": data
218
218
  })
219
219
 
220
- return self._request("POST", f"{self.base_url}/api/v1/memory/batch", json=operations)
220
+ return self._request("POST", f"{self.base_url}/api/v1/memory/batch", json=operations)
221
+
222
+ # ==================== Thread Methods ====================
223
+
224
+ def threads_create(self, title: str, description: str | None = None, metadata: Dict[str, Any] | None = None):
225
+ """
226
+ Create a new thread
227
+
228
+ Args:
229
+ title: Thread title (required)
230
+ description: Optional thread description
231
+ metadata: Optional metadata dictionary
232
+
233
+ Returns:
234
+ Created thread object with id
235
+ """
236
+ body = {"title": title}
237
+ if description:
238
+ body["description"] = description
239
+ if metadata:
240
+ body["metadata"] = metadata
241
+ return self._request("POST", f"{self.base_url}/api/v1/threads", json=body)
242
+
243
+ def threads_list(self, limit: int = 20, offset: int = 0, status: str | None = None):
244
+ """
245
+ List threads with pagination
246
+
247
+ Args:
248
+ limit: Maximum number of threads to return (default: 20)
249
+ offset: Number of threads to skip (default: 0)
250
+ status: Filter by status: 'active', 'archived', or 'completed'
251
+
252
+ Returns:
253
+ Paginated list of threads
254
+ """
255
+ params = {"limit": str(limit), "offset": str(offset)}
256
+ if status:
257
+ params["status"] = status
258
+ return self._request("GET", f"{self.base_url}/api/v1/threads", params=params)
259
+
260
+ def threads_get(self, thread_id: str, include_memories: bool = False, limit: int = 50):
261
+ """
262
+ Get a thread by ID
263
+
264
+ Args:
265
+ thread_id: Thread UUID
266
+ include_memories: If True, include full memory objects (default: False)
267
+ limit: Maximum number of memories to include (default: 50)
268
+
269
+ Returns:
270
+ Thread object with optional memories
271
+ """
272
+ params = {}
273
+ if include_memories:
274
+ params["include_memories"] = "true"
275
+ params["limit"] = str(limit)
276
+ return self._request("GET", f"{self.base_url}/api/v1/threads/{thread_id}", params=params)
277
+
278
+ def threads_update(
279
+ self,
280
+ thread_id: str,
281
+ title: str | None = None,
282
+ description: str | None = None,
283
+ status: str | None = None,
284
+ metadata: Dict[str, Any] | None = None
285
+ ):
286
+ """
287
+ Update a thread
288
+
289
+ Args:
290
+ thread_id: Thread UUID
291
+ title: New title
292
+ description: New description
293
+ status: New status ('active', 'archived', or 'completed')
294
+ metadata: New metadata dictionary
295
+
296
+ Returns:
297
+ Updated thread object
298
+ """
299
+ body = {}
300
+ if title is not None:
301
+ body["title"] = title
302
+ if description is not None:
303
+ body["description"] = description
304
+ if status is not None:
305
+ body["status"] = status
306
+ if metadata is not None:
307
+ body["metadata"] = metadata
308
+ return self._request("PUT", f"{self.base_url}/api/v1/threads/{thread_id}", json=body)
309
+
310
+ def threads_delete(self, thread_id: str, delete_memories: bool = False):
311
+ """
312
+ Delete a thread
313
+
314
+ Args:
315
+ thread_id: Thread UUID
316
+ delete_memories: If True, also delete all associated memories (default: False)
317
+
318
+ Returns:
319
+ Deletion confirmation
320
+ """
321
+ params = {}
322
+ if delete_memories:
323
+ params["delete_memories"] = "true"
324
+ return self._request("DELETE", f"{self.base_url}/api/v1/threads/{thread_id}", params=params)
@@ -0,0 +1,381 @@
1
+ Metadata-Version: 2.4
2
+ Name: sovant
3
+ Version: 1.3.1
4
+ Summary: Sovant Python SDK — governed AI memory layer for AI agents and applications
5
+ Author: Sovant
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://sovant.ai/docs
8
+ Project-URL: Source, https://github.com/hechin91/sovant-ai
9
+ Project-URL: Tracker, https://github.com/hechin91/sovant-ai/issues
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: httpx>=0.27.0
14
+ Requires-Dist: pydantic>=2.8.2
15
+ Dynamic: license-file
16
+
17
+ # Sovant Python SDK
18
+
19
+ **Sovant is a governed AI memory layer for AI agents and applications.**
20
+ Use it to store, search, and recall memories with profile awareness and enterprise-grade control over how memory is captured and used.
21
+
22
+ [![PyPI version](https://img.shields.io/pypi/v/sovant.svg)](https://pypi.org/project/sovant/)
23
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install sovant
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ import os
35
+ from sovant import Sovant, MemoryCreate, SearchQuery
36
+
37
+ client = Sovant(api_key=os.environ["SOVANT_API_KEY"])
38
+
39
+ # Create a memory
40
+ mem = client.memory_create(MemoryCreate(
41
+ data="User prefers dark mode",
42
+ type="preference",
43
+ tags=["ui", "settings"],
44
+ ))
45
+
46
+ # Search memories
47
+ results = client.memory_search(SearchQuery(
48
+ query="user preferences",
49
+ limit=10,
50
+ ))
51
+
52
+ # Update a memory
53
+ updated = client.memory_update(mem["id"], {
54
+ "tags": ["ui", "settings", "theme"],
55
+ })
56
+
57
+ # Delete a memory
58
+ client.memory_delete(mem["id"])
59
+ ```
60
+
61
+ ## Recall vs Search
62
+
63
+ Sovant provides two ways to query memories:
64
+
65
+ - **`memory_recall()`** -- Hybrid recall, profile-aware.
66
+ Use for conversational queries: "What do you know about me?", "What happened on Project X?".
67
+ Uses Sovant's hybrid pipeline (profile fast-path + thread-scoped lexical + vector semantic search) and prioritizes profile facts (name/age/location) when available.
68
+
69
+ - **`memory_search()`** -- Semantic search.
70
+ Use for topic lookup and discovery. Pure vector similarity search without profile logic.
71
+
72
+ ```python
73
+ # Recall for conversational queries (returns { "results": [...], "total": N })
74
+ recall = client.memory_recall(
75
+ query="what do you know about me?",
76
+ limit=10,
77
+ )
78
+ for mem in recall.get("results", []):
79
+ print(mem["content"])
80
+
81
+ # Search for topic discovery
82
+ topics = client.memory_search(SearchQuery(
83
+ query="project updates",
84
+ limit=5,
85
+ ))
86
+ ```
87
+
88
+ ## Working with Threads
89
+
90
+ Threads let you organize related memories into conversations or sessions. Each thread has a `title` and can contain multiple memories.
91
+
92
+ ```python
93
+ import os
94
+ from sovant import Sovant, MemoryCreate
95
+
96
+ client = Sovant(api_key=os.environ["SOVANT_API_KEY"])
97
+
98
+ # Create a new thread
99
+ thread = client.threads_create(
100
+ title="Project Alpha Discussion",
101
+ description="Q1 planning meeting notes",
102
+ metadata={"project": "alpha", "quarter": "Q1"},
103
+ )
104
+
105
+ # Store memories in the thread
106
+ mem1 = client.memory_create(MemoryCreate(
107
+ data="Decided to launch in March",
108
+ type="journal",
109
+ thread_id=thread["id"],
110
+ ))
111
+
112
+ mem2 = client.memory_create(MemoryCreate(
113
+ data="Budget approved: $50k",
114
+ type="insight",
115
+ thread_id=thread["id"],
116
+ ))
117
+
118
+ # Recall memories from this specific thread
119
+ thread_recall = client.memory_recall(
120
+ query="launch date",
121
+ thread_id=thread["id"],
122
+ limit=10,
123
+ )
124
+ # thread_recall["results"] contains the matching memories
125
+
126
+ # Get thread with all its memories
127
+ thread_with_memories = client.threads_get(
128
+ thread["id"],
129
+ include_memories=True,
130
+ limit=50,
131
+ )
132
+
133
+ # List all threads
134
+ threads = client.threads_list(limit=20, offset=0)
135
+
136
+ # Update thread
137
+ updated = client.threads_update(
138
+ thread["id"],
139
+ title="Project Alpha - Q1 Launch",
140
+ status="completed",
141
+ )
142
+
143
+ # Delete thread (keeps memories by default)
144
+ client.threads_delete(thread["id"])
145
+
146
+ # Delete thread AND all its memories
147
+ client.threads_delete(thread["id"], delete_memories=True)
148
+ ```
149
+
150
+ ## Configuration
151
+
152
+ ```python
153
+ from sovant import Sovant
154
+
155
+ client = Sovant(
156
+ api_key="sk_live_...", # Required (or set SOVANT_API_KEY env var)
157
+ base_url="https://sovant.ai", # Optional, defaults to https://sovant.ai
158
+ timeout=30.0, # Optional, request timeout in seconds (default: 30.0)
159
+ max_retries=3, # Optional, max retry attempts (default: 3)
160
+ )
161
+ ```
162
+
163
+ The SDK handles authentication via the `Authorization: Bearer` header.
164
+
165
+ ## API Reference
166
+
167
+ ### Memory Operations
168
+
169
+ #### Create Memory
170
+
171
+ ```python
172
+ from sovant import MemoryCreate
173
+
174
+ memory = client.memory_create(MemoryCreate(
175
+ data="Customer contacted support about billing",
176
+ type="observation", # 'journal' | 'insight' | 'observation' | 'task' | 'preference'
177
+ tags=["support", "billing"],
178
+ metadata={"ticket_id": "12345"},
179
+ thread_id="thread_abc123", # Optional thread association
180
+ ))
181
+ ```
182
+
183
+ #### Get Memory by ID
184
+
185
+ ```python
186
+ memory = client.memory_get("mem_123abc")
187
+ ```
188
+
189
+ #### Update Memory
190
+
191
+ ```python
192
+ updated = client.memory_update("mem_123abc", {
193
+ "tags": ["support", "billing", "resolved"],
194
+ })
195
+ ```
196
+
197
+ #### Delete Memory
198
+
199
+ ```python
200
+ client.memory_delete("mem_123abc")
201
+ ```
202
+
203
+ #### Search Memories
204
+
205
+ ```python
206
+ from sovant import SearchQuery
207
+
208
+ # Semantic search
209
+ results = client.memory_search(SearchQuery(
210
+ query="customer preferences about notifications",
211
+ limit=10,
212
+ type="preference",
213
+ ))
214
+
215
+ # Filter-based search
216
+ results = client.memory_search(SearchQuery(
217
+ tags=["settings", "notifications"],
218
+ from_date="2024-01-01",
219
+ to_date="2024-12-31",
220
+ limit=20,
221
+ ))
222
+ ```
223
+
224
+ #### Recall Memories
225
+
226
+ Recall returns `{ "results": [...], "total": N, "query_type": "hybrid" }`.
227
+
228
+ ```python
229
+ # Hybrid recall with profile awareness
230
+ recall = client.memory_recall(
231
+ query="what do you know about me?",
232
+ limit=10,
233
+ )
234
+ for mem in recall.get("results", []):
235
+ print(mem["content"], mem.get("relevance"))
236
+
237
+ # Thread-scoped recall
238
+ recall = client.memory_recall(
239
+ query="launch timeline",
240
+ thread_id="thread_abc123",
241
+ limit=5,
242
+ )
243
+ ```
244
+
245
+ #### Batch Create
246
+
247
+ ```python
248
+ results = client.memory_create_batch([
249
+ {"data": "First memory", "type": "journal"},
250
+ {"data": "Second memory", "type": "insight", "tags": ["important"]},
251
+ ])
252
+ ```
253
+
254
+ ### Thread Management
255
+
256
+ ```python
257
+ # Create a thread
258
+ thread = client.threads_create(
259
+ title="Customer Support Session",
260
+ metadata={"user_id": "user_123"},
261
+ )
262
+
263
+ # List threads
264
+ threads = client.threads_list(limit=10, offset=0)
265
+
266
+ # Get thread by ID (with memories)
267
+ thread = client.threads_get("thread_abc123", include_memories=True)
268
+
269
+ # Update thread
270
+ updated = client.threads_update(
271
+ "thread_abc123",
272
+ title="Resolved: Billing Issue",
273
+ status="completed",
274
+ )
275
+
276
+ # Delete thread
277
+ client.threads_delete("thread_abc123")
278
+ ```
279
+
280
+ ## Memory Types
281
+
282
+ - **journal** -- Chronological entries and logs
283
+ - **insight** -- Derived patterns and conclusions
284
+ - **observation** -- Factual, observed information
285
+ - **task** -- Action items and todos
286
+ - **preference** -- User preferences and settings
287
+
288
+ ## Error Handling
289
+
290
+ The SDK raises `SovantError` for all API errors:
291
+
292
+ ```python
293
+ from sovant import Sovant, SovantError
294
+
295
+ try:
296
+ memory = client.memory_get("invalid_id")
297
+ except SovantError as e:
298
+ print(f"Error: {e}")
299
+ print(f"Code: {e.code}") # e.g. "NOT_FOUND", "HTTP_401"
300
+ print(f"Status: {e.status}") # e.g. 404, 401
301
+
302
+ if e.status == 404:
303
+ print("Memory not found")
304
+ elif e.status == 401:
305
+ print("Invalid API key")
306
+ elif e.status == 429:
307
+ print("Rate limited -- SDK retries automatically")
308
+ ```
309
+
310
+ The SDK automatically retries on rate limit (429) and server errors (5xx) with exponential backoff.
311
+
312
+ ## Examples
313
+
314
+ ### Customer Support Integration
315
+
316
+ ```python
317
+ from sovant import MemoryCreate
318
+
319
+ # Track customer interaction
320
+ interaction = client.memory_create(MemoryCreate(
321
+ data="Customer reported slow dashboard loading",
322
+ type="observation",
323
+ thread_id=f"ticket_{ticket_id}",
324
+ tags=["support", "performance", "dashboard"],
325
+ metadata={
326
+ "ticket_id": ticket_id,
327
+ "priority": "high",
328
+ },
329
+ ))
330
+
331
+ # Record resolution
332
+ resolution = client.memory_create(MemoryCreate(
333
+ data="Resolved by clearing cache and upgrading plan",
334
+ type="insight",
335
+ thread_id=f"ticket_{ticket_id}",
336
+ tags=["support", "resolved"],
337
+ ))
338
+ ```
339
+
340
+ ### User Preference Tracking
341
+
342
+ ```python
343
+ from sovant import MemoryCreate, SearchQuery
344
+
345
+ # Store preference
346
+ client.memory_create(MemoryCreate(
347
+ data="User prefers email notifications over SMS",
348
+ type="preference",
349
+ tags=["notifications", "email"],
350
+ ))
351
+
352
+ # Query preferences
353
+ preferences = client.memory_search(SearchQuery(
354
+ query="notification preferences",
355
+ type="preference",
356
+ ))
357
+ ```
358
+
359
+ ## Rate Limiting
360
+
361
+ The API enforces 60 requests per minute. The SDK automatically retries rate-limited requests with exponential backoff. Rate limit headers are included in responses:
362
+
363
+ - `X-RateLimit-Limit` -- Request limit per window
364
+ - `X-RateLimit-Remaining` -- Remaining requests
365
+ - `X-RateLimit-Reset` -- Reset timestamp
366
+
367
+ ## Support
368
+
369
+ - Documentation: [https://sovant.ai/docs](https://sovant.ai/docs)
370
+ - Issues: [GitHub Issues](https://github.com/hechin91/sovant-ai/issues)
371
+
372
+ ## License & Use
373
+
374
+ - This SDK is MIT-licensed for integration convenience.
375
+ - The Sovant API and platform are proprietary to Sovant Technologies Sdn. Bhd.
376
+ - You may use this SDK to integrate with Sovant's hosted API.
377
+ - Hosting/redistributing the Sovant backend or any proprietary components is not permitted.
378
+
379
+ ## License
380
+
381
+ MIT -- See [LICENSE](LICENSE) file for details.
@@ -1,14 +1,14 @@
1
- sovant/__init__.py,sha256=1GYQzmb1Ni5-y874HOs1J-rYrpx-iBzR4_cCEBFIjpk,122
1
+ sovant/__init__.py,sha256=t0XgMU44um_gJBNGkg8QnaEC1SXsIh56IF2TrdDgbuY,122
2
2
  sovant/base_client.py,sha256=Vmn6OGywGwLbH5cEeflSjVOFwn5iX_YdfTdUq9pWWxA,8778
3
- sovant/client.py,sha256=KQZ5yi4-5gPc7QxuRzJC0tBSfZ6Cohkdl397yuCCA0I,8331
3
+ sovant/client.py,sha256=8VnTPSli1QgRky9PqUb9OAKdbMPTfXlGoUkmAYsFfCw,11804
4
4
  sovant/exceptions.py,sha256=MQMSgk7ckXnAbe7hPPpbKOnRBHSPxuCY7WSjqfJAvd0,1557
5
5
  sovant/models.py,sha256=avDAITMptDDdDfNH_ed854Q7kF6z_1OzjwJ9Xeft_-8,979
6
6
  sovant/types.py,sha256=gnvdXksJt8LObti7nc6eHSBCB7Pz7SNpS5o_HRTq_kA,6098
7
7
  sovant/resources/__init__.py,sha256=cPFIM7h8duviDdeHudnVEAmv3F89RHQxdH5BCRWFteQ,193
8
8
  sovant/resources/memories.py,sha256=bKKE0uWqFkPa1OEvbK1LrdSD7v6N04RhJ_2VDoPPQBA,11379
9
9
  sovant/resources/threads.py,sha256=mN29xP0JODmZBKyfhpeqJyViUWNVMAx3TlYAW-1ruTs,12558
10
- sovant-1.2.0.dist-info/licenses/LICENSE,sha256=rnNP6-elrMIlQ9jf2aqnHZMyyQ_wvF3y1hTpVUusCWU,1062
11
- sovant-1.2.0.dist-info/METADATA,sha256=cPnJTbYhtsWr77V7AJ8CkWCnZwk3p0Uz0eI7vAxK1KA,12753
12
- sovant-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- sovant-1.2.0.dist-info/top_level.txt,sha256=za6eVEsYd_ZQQs8vrmEWNcAR58r1wCDge_jA60e4CvQ,7
14
- sovant-1.2.0.dist-info/RECORD,,
10
+ sovant-1.3.1.dist-info/licenses/LICENSE,sha256=rnNP6-elrMIlQ9jf2aqnHZMyyQ_wvF3y1hTpVUusCWU,1062
11
+ sovant-1.3.1.dist-info/METADATA,sha256=ej271WYl3oSvAlmWRCVIjr_ebFSkgzw4-C3NVCOm01k,9515
12
+ sovant-1.3.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ sovant-1.3.1.dist-info/top_level.txt,sha256=za6eVEsYd_ZQQs8vrmEWNcAR58r1wCDge_jA60e4CvQ,7
14
+ sovant-1.3.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,512 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: sovant
3
- Version: 1.2.0
4
- Summary: Sovant Memory-as-a-Service Python SDK
5
- Author: Sovant
6
- License: MIT
7
- Project-URL: Documentation, https://sovant.ai/docs
8
- Project-URL: Source, https://github.com/hechin91/sovant-ai
9
- Project-URL: Tracker, https://github.com/hechin91/sovant-ai/issues
10
- Requires-Python: >=3.10
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Requires-Dist: httpx>=0.27.0
14
- Requires-Dist: pydantic>=2.8.2
15
- Dynamic: license-file
16
-
17
- # Sovant Python SDK
18
-
19
- **Sovant is a governed AI memory layer for AI agents and applications.**
20
- Use it to store, search, and recall memories with profile awareness and enterprise-grade control over how memory is captured and used.
21
-
22
- [![PyPI version](https://img.shields.io/pypi/v/sovant.svg)](https://pypi.org/project/sovant/)
23
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
24
-
25
- ## Installation
26
-
27
- ```bash
28
- pip install sovant
29
- ```
30
-
31
- ## Quick Start
32
-
33
- ```python
34
- from sovant import Sovant
35
-
36
- # Initialize the client
37
- client = Sovant(api_key="sk_live_your_api_key_here", base_url="https://sovant.ai")
38
-
39
- # Create a memory
40
- mem = client.memory.create({
41
- "content": "User prefers dark mode",
42
- "type": "preference",
43
- "tags": ["ui", "settings"]
44
- })
45
-
46
- # Search memories
47
- results = client.memory.search({
48
- "query": "user preferences",
49
- "limit": 10
50
- })
51
-
52
- # Update a memory
53
- updated = client.memory.update(mem["id"], {
54
- "tags": ["ui", "settings", "theme"]
55
- })
56
-
57
- # Delete a memory
58
- client.memory.delete(mem["id"])
59
- ```
60
-
61
- ## Recall vs Search
62
-
63
- Sovant provides two ways to query memories:
64
-
65
- - **`memory_recall()`** – Hybrid recall, profile-aware
66
- Use for conversational queries: "What do you know about me?", "What happened on Project X?".
67
- Uses Sovant's hybrid pipeline (profile fast-path + thread-scoped lexical + vector semantic search) and prioritizes profile facts (name/age/location) when available.
68
-
69
- - **`memory_search()`** – Semantic search
70
- Use for topic lookup and discovery. Pure vector similarity search without profile logic.
71
- Behavior unchanged from previous versions.
72
-
73
- ```python
74
- # Recall for conversational queries
75
- context = client.memory_recall(
76
- query="what do you know about me?",
77
- limit=10
78
- )
79
-
80
- # Search for topic discovery
81
- topics = client.memory_search({
82
- "query": "project updates",
83
- "limit": 5
84
- })
85
- ```
86
-
87
- ## Chat in 60 Seconds
88
-
89
- Stream real-time chat responses with memory context:
90
-
91
- ```python
92
- from sovant import Sovant
93
- import sys
94
-
95
- client = Sovant(api_key="sk_live_your_api_key_here", base_url="https://sovant.ai")
96
-
97
- # Create a chat session
98
- session = client.chat.create_session({"title": "Demo"})
99
-
100
- # Stream a response
101
- stream = client.chat.send_message(
102
- session["id"],
103
- "hello",
104
- {
105
- "provider": "openai",
106
- "model": "gpt-4o-mini",
107
- "use_memory": True
108
- },
109
- stream=True
110
- )
111
-
112
- for ev in stream:
113
- if ev["type"] == "delta":
114
- sys.stdout.write(ev.get("data", ""))
115
- elif ev["type"] == "done":
116
- print("\n[done]")
117
-
118
- # Get chat history
119
- messages = client.chat.get_messages(session["id"])
120
- ```
121
-
122
- ## Profile Recall Helpers
123
-
124
- Save and recall user profile facts with canonical patterns:
125
-
126
- ```python
127
- # Extract profile entity from text
128
- fact = client.recall.extract_profile("i'm from kuching")
129
- # -> {"entity": "location", "value": "kuching"} | None
130
-
131
- if fact:
132
- client.recall.save_profile_fact(fact) # canonicalizes and persists
133
-
134
- # Get all profile facts
135
- profile = client.recall.get_profile_facts()
136
- # -> {"name": "...", "age": "...", "location": "...", "preferences": [...]}
137
- ```
138
-
139
- ## Configuration
140
-
141
- ```python
142
- from sovant import Sovant
143
-
144
- client = Sovant(
145
- api_key="sk_live_your_api_key_here", # Required
146
- base_url="https://sovant.ai", # Optional, API endpoint
147
- timeout=30.0, # Optional, request timeout in seconds (default: 30.0)
148
- max_retries=3, # Optional, max retry attempts (default: 3)
149
- debug=False, # Optional, enable debug logging (default: False)
150
- )
151
- ```
152
-
153
- The SDK handles dual authentication automatically, preferring the `x-sovant-api-key` header over `Authorization: Bearer`.
154
-
155
- ## API Reference
156
-
157
- ### Memory Operations
158
-
159
- #### Create Memory
160
-
161
- ```python
162
- memory = client.memory.create({
163
- "content": "Customer contacted support about billing",
164
- "type": "observation", # 'journal' | 'insight' | 'observation' | 'task' | 'preference'
165
- "tags": ["support", "billing"],
166
- "metadata": {"ticket_id": "12345"},
167
- "thread_id": "thread_abc123", # Optional thread association
168
- })
169
- ```
170
-
171
- #### List Memories
172
-
173
- ```python
174
- memories = client.memory.list({
175
- "limit": 20, # Max items per page (default: 20)
176
- "offset": 0, # Pagination offset
177
- "tags": ["billing"], # Filter by tags
178
- "type": "observation", # Filter by type
179
- "is_archived": False, # Filter archived status
180
- })
181
-
182
- print(memories["memories"]) # Array of memories
183
- print(memories["total"]) # Total count
184
- print(memories["has_more"]) # More pages available
185
- ```
186
-
187
- #### Get Memory by ID
188
-
189
- ```python
190
- memory = client.memory.get("mem_123abc")
191
- ```
192
-
193
- #### Update Memory (Partial)
194
-
195
- ```python
196
- updated = client.memory.update("mem_123abc", {
197
- "tags": ["support", "billing", "resolved"],
198
- "metadata": {
199
- **memory.get("metadata", {}),
200
- "resolved": True,
201
- },
202
- "is_archived": True,
203
- })
204
- ```
205
-
206
- #### Replace Memory (Full)
207
-
208
- ```python
209
- replaced = client.memory.put("mem_123abc", {
210
- "content": "Updated content here", # Required for PUT
211
- "type": "observation",
212
- "tags": ["updated"],
213
- })
214
- ```
215
-
216
- #### Delete Memory
217
-
218
- ```python
219
- client.memory.delete("mem_123abc")
220
- ```
221
-
222
- #### Search Memories
223
-
224
- ```python
225
- # Semantic search
226
- semantic_results = client.memory.search({
227
- "query": "customer preferences about notifications",
228
- "limit": 10,
229
- "type": "preference",
230
- })
231
-
232
- # Filter-based search
233
- filter_results = client.memory.search({
234
- "tags": ["settings", "notifications"],
235
- "from_date": "2024-01-01",
236
- "to_date": "2024-12-31",
237
- "limit": 20,
238
- })
239
- ```
240
-
241
- #### Batch Operations
242
-
243
- ```python
244
- batch = client.memory.batch({
245
- "operations": [
246
- {
247
- "op": "create",
248
- "data": {
249
- "content": "First memory",
250
- "type": "journal",
251
- },
252
- },
253
- {
254
- "op": "update",
255
- "id": "mem_123abc",
256
- "data": {
257
- "tags": ["updated"],
258
- },
259
- },
260
- {
261
- "op": "delete",
262
- "id": "mem_456def",
263
- },
264
- ],
265
- })
266
-
267
- print(batch["results"]) # Individual operation results
268
- print(batch["summary"]) # Summary statistics
269
- ```
270
-
271
- ### Thread Management
272
-
273
- Associate memories with conversation threads:
274
-
275
- ```python
276
- # Create a thread
277
- thread = client.threads.create({
278
- "title": "Customer Support Session",
279
- "metadata": {"user_id": "user_123"}
280
- })
281
-
282
- # List threads
283
- threads = client.threads.list({
284
- "limit": 10,
285
- "offset": 0
286
- })
287
-
288
- # Get thread by ID
289
- thread = client.threads.get("thread_abc123")
290
-
291
- # Update thread
292
- updated_thread = client.threads.update("thread_abc123", {
293
- "title": "Resolved: Billing Issue",
294
- "metadata": {"status": "resolved"}
295
- })
296
-
297
- # Delete thread
298
- client.threads.delete("thread_abc123")
299
-
300
- # Link memory to thread
301
- client.threads.link_memory("thread_abc123", "mem_123abc")
302
-
303
- # Create memories within a thread
304
- memory1 = client.memory.create({
305
- "content": "User asked about pricing",
306
- "type": "observation",
307
- "thread_id": "thread_abc123",
308
- })
309
-
310
- memory2 = client.memory.create({
311
- "content": "User selected enterprise plan",
312
- "type": "observation",
313
- "thread_id": "thread_abc123",
314
- })
315
-
316
- # List memories in a thread
317
- thread_memories = client.memory.list({
318
- "thread_id": "thread_abc123",
319
- })
320
- ```
321
-
322
- ### API Key Management
323
-
324
- Manage API keys programmatically:
325
-
326
- ```python
327
- # List all API keys
328
- keys = client.keys.list()
329
- print(keys) # Array of key objects
330
-
331
- # Create a new API key
332
- new_key = client.keys.create({"name": "CI key"})
333
- print(new_key["key"]) # The actual secret key (only shown once!)
334
-
335
- # Update key metadata
336
- client.keys.update(new_key["id"], {"name": "Production key"})
337
-
338
- # Revoke a key
339
- client.keys.revoke(new_key["id"])
340
- ```
341
-
342
- ## Memory Types
343
-
344
- - **journal** - Chronological entries and logs
345
- - **insight** - Derived patterns and conclusions
346
- - **observation** - Factual, observed information
347
- - **task** - Action items and todos
348
- - **preference** - User preferences and settings
349
-
350
- ## Error Handling
351
-
352
- The SDK provides typed errors for better error handling:
353
-
354
- ```python
355
- from sovant import Sovant, SovantError, AuthError, RateLimitError, NetworkError, TimeoutError
356
-
357
- try:
358
- memory = client.memory.get("invalid_id")
359
- except AuthError as e:
360
- print(f"Authentication failed: {e}")
361
- # Handle authentication error
362
- except RateLimitError as e:
363
- print(f"Rate limit exceeded: {e}")
364
- print(f"Retry after: {e.retry_after}")
365
- # Handle rate limiting
366
- except NetworkError as e:
367
- print(f"Network error: {e}")
368
- # Handle network issues
369
- except TimeoutError as e:
370
- print(f"Request timed out: {e}")
371
- # Handle timeout
372
- except SovantError as e:
373
- print(f"API Error: {e}")
374
- print(f"Status: {e.status}")
375
- print(f"Request ID: {e.request_id}")
376
-
377
- if e.status == 404:
378
- # Handle not found
379
- pass
380
- elif e.status == 400:
381
- # Handle bad request
382
- pass
383
- ```
384
-
385
- ## Advanced Features
386
-
387
- ### Retry Configuration
388
-
389
- The SDK automatically retries failed requests with exponential backoff:
390
-
391
- ```python
392
- client = Sovant(
393
- api_key="sk_live_...",
394
- max_retries=5, # Increase retry attempts
395
- timeout=60.0, # Increase timeout for slow connections
396
- )
397
- ```
398
-
399
- ### Debug Mode
400
-
401
- Enable debug logging to see detailed request/response information:
402
-
403
- ```python
404
- client = Sovant(
405
- api_key="sk_live_...",
406
- debug=True, # Enable debug output
407
- )
408
- ```
409
-
410
- ### Custom Base URL
411
-
412
- Connect to different environments:
413
-
414
- ```python
415
- client = Sovant(
416
- api_key="sk_live_...",
417
- base_url="https://staging.sovant.ai",
418
- )
419
- ```
420
-
421
- ## Best Practices
422
-
423
- 1. **Use appropriate memory types** - Choose the correct type for your use case
424
- 2. **Add meaningful tags** - Tags improve searchability and organization
425
- 3. **Use threads** - Group related memories together
426
- 4. **Handle errors gracefully** - Implement proper error handling
427
- 5. **Batch operations** - Use batch API for multiple operations
428
- 6. **Archive don't delete** - Consider archiving instead of deleting
429
-
430
- ## Examples
431
-
432
- ### Customer Support Integration
433
-
434
- ```python
435
- # Track customer interaction
436
- interaction = client.memory.create({
437
- "content": "Customer reported slow dashboard loading",
438
- "type": "observation",
439
- "thread_id": f"ticket_{ticket_id}",
440
- "tags": ["support", "performance", "dashboard"],
441
- "metadata": {
442
- "ticket_id": ticket_id,
443
- "customer_id": customer_id,
444
- "priority": "high",
445
- },
446
- })
447
-
448
- # Record resolution
449
- resolution = client.memory.create({
450
- "content": "Resolved by clearing cache and upgrading plan",
451
- "type": "insight",
452
- "thread_id": f"ticket_{ticket_id}",
453
- "tags": ["support", "resolved"],
454
- "metadata": {
455
- "ticket_id": ticket_id,
456
- "resolution_time": "2h",
457
- },
458
- })
459
- ```
460
-
461
- ### User Preference Tracking
462
-
463
- ```python
464
- # Store preference
465
- preference = client.memory.create({
466
- "content": "User prefers email notifications over SMS",
467
- "type": "preference",
468
- "tags": ["notifications", "email", "settings"],
469
- "metadata": {
470
- "user_id": user_id,
471
- "setting": "notification_channel",
472
- "value": "email",
473
- },
474
- })
475
-
476
- # Query preferences
477
- preferences = client.memory.search({
478
- "query": "notification preferences",
479
- "type": "preference",
480
- "tags": ["notifications"],
481
- })
482
- ```
483
-
484
- ## Rate Limiting
485
-
486
- The API implements rate limiting. The SDK automatically handles rate limit responses with retries. Rate limit headers are included in responses:
487
-
488
- - `X-RateLimit-Limit` - Request limit per window
489
- - `X-RateLimit-Remaining` - Remaining requests
490
- - `X-RateLimit-Reset` - Reset timestamp
491
-
492
- ## Support
493
-
494
- - Documentation: [https://sovant.ai/docs](https://sovant.ai/docs)
495
- - API/Auth docs: [https://sovant.ai/docs/security/auth](https://sovant.ai/docs/security/auth)
496
- - Issues: [GitHub Issues](https://github.com/sovant-ai/python-sdk/issues)
497
- - Support: support@sovant.ai
498
-
499
- ## Changelog
500
-
501
- See [CHANGELOG.md](./CHANGELOG.md) for a detailed history of changes.
502
-
503
- ## License & Use
504
-
505
- - This SDK is MIT-licensed for integration convenience.
506
- - The Sovant API and platform are proprietary to Sovant Technologies Sdn. Bhd.
507
- - You may use this SDK to integrate with Sovant's hosted API.
508
- - Hosting/redistributing the Sovant backend or any proprietary components is not permitted.
509
-
510
- ## License
511
-
512
- MIT - See [LICENSE](LICENSE) file for details.