agnt5 0.3.2a1__cp310-abi3-manylinux_2_34_aarch64.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.
Potentially problematic release.
This version of agnt5 might be problematic. Click here for more details.
- agnt5/__init__.py +119 -0
- agnt5/_compat.py +16 -0
- agnt5/_core.abi3.so +0 -0
- agnt5/_retry_utils.py +196 -0
- agnt5/_schema_utils.py +312 -0
- agnt5/_sentry.py +515 -0
- agnt5/_telemetry.py +279 -0
- agnt5/agent/__init__.py +48 -0
- agnt5/agent/context.py +581 -0
- agnt5/agent/core.py +1782 -0
- agnt5/agent/decorator.py +112 -0
- agnt5/agent/handoff.py +105 -0
- agnt5/agent/registry.py +68 -0
- agnt5/agent/result.py +39 -0
- agnt5/checkpoint.py +246 -0
- agnt5/client.py +1556 -0
- agnt5/context.py +288 -0
- agnt5/emit.py +197 -0
- agnt5/entity.py +1230 -0
- agnt5/events.py +567 -0
- agnt5/exceptions.py +110 -0
- agnt5/function.py +330 -0
- agnt5/journal.py +212 -0
- agnt5/lm.py +1266 -0
- agnt5/memoization.py +379 -0
- agnt5/memory.py +521 -0
- agnt5/tool.py +721 -0
- agnt5/tracing.py +300 -0
- agnt5/types.py +111 -0
- agnt5/version.py +19 -0
- agnt5/worker.py +2094 -0
- agnt5/workflow.py +1632 -0
- agnt5-0.3.2a1.dist-info/METADATA +26 -0
- agnt5-0.3.2a1.dist-info/RECORD +35 -0
- agnt5-0.3.2a1.dist-info/WHEEL +4 -0
agnt5/memory.py
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
"""Memory classes for AGNT5 SDK.
|
|
2
|
+
|
|
3
|
+
Provides memory abstractions for workflows and agents:
|
|
4
|
+
- ConversationMemory: KV-backed message history for sessions
|
|
5
|
+
- SemanticMemory: Vector-backed semantic search for user/tenant memory (Phase 3)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from ._telemetry import setup_module_logger
|
|
15
|
+
from .lm import Message, MessageRole
|
|
16
|
+
|
|
17
|
+
logger = setup_module_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class MemoryMessage:
|
|
22
|
+
"""Message stored in conversation memory.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
role: Message role (user, assistant, system)
|
|
26
|
+
content: Message content text
|
|
27
|
+
timestamp: Unix timestamp when message was added
|
|
28
|
+
metadata: Optional additional metadata
|
|
29
|
+
"""
|
|
30
|
+
role: str
|
|
31
|
+
content: str
|
|
32
|
+
timestamp: float = field(default_factory=time.time)
|
|
33
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""Convert to dictionary for serialization."""
|
|
37
|
+
return {
|
|
38
|
+
"role": self.role,
|
|
39
|
+
"content": self.content,
|
|
40
|
+
"timestamp": self.timestamp,
|
|
41
|
+
"metadata": self.metadata,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_dict(cls, data: Dict[str, Any]) -> "MemoryMessage":
|
|
46
|
+
"""Create from dictionary."""
|
|
47
|
+
return cls(
|
|
48
|
+
role=data.get("role", "user"),
|
|
49
|
+
content=data.get("content", ""),
|
|
50
|
+
timestamp=data.get("timestamp", time.time()),
|
|
51
|
+
metadata=data.get("metadata", {}),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def to_lm_message(self) -> Message:
|
|
55
|
+
"""Convert to LM Message for agent prompts."""
|
|
56
|
+
role_map = {
|
|
57
|
+
"user": MessageRole.USER,
|
|
58
|
+
"assistant": MessageRole.ASSISTANT,
|
|
59
|
+
"system": MessageRole.SYSTEM,
|
|
60
|
+
}
|
|
61
|
+
return Message(
|
|
62
|
+
role=role_map.get(self.role, MessageRole.USER),
|
|
63
|
+
content=self.content,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ConversationMemory:
|
|
68
|
+
"""KV-backed conversation memory for session history.
|
|
69
|
+
|
|
70
|
+
Stores sequential message history for a session, enabling multi-turn
|
|
71
|
+
conversations. Messages are persisted to the platform and loaded on demand.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
```python
|
|
75
|
+
# In a workflow
|
|
76
|
+
@workflow
|
|
77
|
+
async def chat_workflow(ctx: WorkflowContext, message: str) -> str:
|
|
78
|
+
# Load conversation history
|
|
79
|
+
conversation = ConversationMemory(ctx.session_id)
|
|
80
|
+
history = await conversation.get_messages()
|
|
81
|
+
|
|
82
|
+
# Process with agent
|
|
83
|
+
result = await agent.run_sync(message, history=history)
|
|
84
|
+
|
|
85
|
+
# Save new messages
|
|
86
|
+
await conversation.add("user", message)
|
|
87
|
+
await conversation.add("assistant", result.output)
|
|
88
|
+
|
|
89
|
+
return result.output
|
|
90
|
+
```
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, session_id: str) -> None:
|
|
94
|
+
"""Initialize conversation memory for a session.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
session_id: Unique identifier for the conversation session
|
|
98
|
+
"""
|
|
99
|
+
self.session_id = session_id
|
|
100
|
+
self._entity_key = f"conversation:{session_id}"
|
|
101
|
+
self._entity_type = "ConversationMemory"
|
|
102
|
+
self._state_adapter = None
|
|
103
|
+
self._cache: Optional[List[MemoryMessage]] = None
|
|
104
|
+
|
|
105
|
+
def _get_adapter(self):
|
|
106
|
+
"""Get or create state adapter for persistence."""
|
|
107
|
+
if self._state_adapter is None:
|
|
108
|
+
from .entity import _get_state_adapter, EntityStateAdapter
|
|
109
|
+
try:
|
|
110
|
+
self._state_adapter = _get_state_adapter()
|
|
111
|
+
except RuntimeError:
|
|
112
|
+
# Not in worker context - create standalone adapter
|
|
113
|
+
self._state_adapter = EntityStateAdapter()
|
|
114
|
+
return self._state_adapter
|
|
115
|
+
|
|
116
|
+
async def get_messages(self, limit: int = 50) -> List[MemoryMessage]:
|
|
117
|
+
"""Get recent messages from conversation history.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
limit: Maximum number of messages to return (most recent)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of MemoryMessage objects, ordered chronologically
|
|
124
|
+
"""
|
|
125
|
+
adapter = self._get_adapter()
|
|
126
|
+
|
|
127
|
+
# Load session data from storage
|
|
128
|
+
session_data = await adapter.load_state(self._entity_type, self._entity_key)
|
|
129
|
+
|
|
130
|
+
if not session_data:
|
|
131
|
+
return []
|
|
132
|
+
|
|
133
|
+
messages_data = session_data.get("messages", [])
|
|
134
|
+
|
|
135
|
+
# Convert to MemoryMessage objects
|
|
136
|
+
messages = [MemoryMessage.from_dict(m) for m in messages_data]
|
|
137
|
+
|
|
138
|
+
# Apply limit (return most recent)
|
|
139
|
+
if limit and len(messages) > limit:
|
|
140
|
+
messages = messages[-limit:]
|
|
141
|
+
|
|
142
|
+
# Cache for potential add() calls
|
|
143
|
+
self._cache = messages
|
|
144
|
+
|
|
145
|
+
return messages
|
|
146
|
+
|
|
147
|
+
async def add(self, role: str, content: str, metadata: Optional[Dict[str, Any]] = None) -> None:
|
|
148
|
+
"""Add a message to the conversation.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
role: Message role ("user", "assistant", "system")
|
|
152
|
+
content: Message content text
|
|
153
|
+
metadata: Optional additional metadata to store
|
|
154
|
+
"""
|
|
155
|
+
adapter = self._get_adapter()
|
|
156
|
+
|
|
157
|
+
# Load current state with version for optimistic locking
|
|
158
|
+
current_state, current_version = await adapter.load_with_version(
|
|
159
|
+
self._entity_type, self._entity_key
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Get existing messages or start fresh
|
|
163
|
+
messages_data = current_state.get("messages", []) if current_state else []
|
|
164
|
+
|
|
165
|
+
# Create new message
|
|
166
|
+
new_message = MemoryMessage(
|
|
167
|
+
role=role,
|
|
168
|
+
content=content,
|
|
169
|
+
timestamp=time.time(),
|
|
170
|
+
metadata=metadata or {},
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Append message
|
|
174
|
+
messages_data.append(new_message.to_dict())
|
|
175
|
+
|
|
176
|
+
# Build session data
|
|
177
|
+
now = time.time()
|
|
178
|
+
session_data = {
|
|
179
|
+
"session_id": self.session_id,
|
|
180
|
+
"created_at": current_state.get("created_at", now) if current_state else now,
|
|
181
|
+
"last_message_at": now,
|
|
182
|
+
"message_count": len(messages_data),
|
|
183
|
+
"messages": messages_data,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Save to storage
|
|
187
|
+
try:
|
|
188
|
+
await adapter.save_state(
|
|
189
|
+
self._entity_type,
|
|
190
|
+
self._entity_key,
|
|
191
|
+
session_data,
|
|
192
|
+
current_version,
|
|
193
|
+
)
|
|
194
|
+
logger.debug(f"Saved message to conversation {self.session_id}: {role}")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"Failed to save message to conversation {self.session_id}: {e}")
|
|
197
|
+
raise
|
|
198
|
+
|
|
199
|
+
async def clear(self) -> None:
|
|
200
|
+
"""Clear all messages in this conversation."""
|
|
201
|
+
adapter = self._get_adapter()
|
|
202
|
+
|
|
203
|
+
# Load current version for optimistic locking
|
|
204
|
+
_, current_version = await adapter.load_with_version(
|
|
205
|
+
self._entity_type, self._entity_key
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Save empty session
|
|
209
|
+
now = time.time()
|
|
210
|
+
session_data = {
|
|
211
|
+
"session_id": self.session_id,
|
|
212
|
+
"created_at": now,
|
|
213
|
+
"last_message_at": now,
|
|
214
|
+
"message_count": 0,
|
|
215
|
+
"messages": [],
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
await adapter.save_state(
|
|
220
|
+
self._entity_type,
|
|
221
|
+
self._entity_key,
|
|
222
|
+
session_data,
|
|
223
|
+
current_version,
|
|
224
|
+
)
|
|
225
|
+
self._cache = []
|
|
226
|
+
logger.info(f"Cleared conversation {self.session_id}")
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(f"Failed to clear conversation {self.session_id}: {e}")
|
|
229
|
+
raise
|
|
230
|
+
|
|
231
|
+
async def get_as_lm_messages(self, limit: int = 50) -> List[Message]:
|
|
232
|
+
"""Get messages formatted for LLM consumption.
|
|
233
|
+
|
|
234
|
+
Convenience method that returns messages as LM Message objects,
|
|
235
|
+
ready to pass to agent.run() or lm.generate().
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
limit: Maximum number of messages to return
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
List of Message objects for LM API
|
|
242
|
+
"""
|
|
243
|
+
messages = await self.get_messages(limit=limit)
|
|
244
|
+
return [m.to_lm_message() for m in messages]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class MemoryScope:
|
|
248
|
+
"""Memory scope for semantic memory.
|
|
249
|
+
|
|
250
|
+
Scopes determine the isolation level of memories:
|
|
251
|
+
- USER: Isolated per user (most common)
|
|
252
|
+
- TENANT: Shared across users in a tenant
|
|
253
|
+
- AGENT: Isolated per agent instance
|
|
254
|
+
- SESSION: Isolated per session (ephemeral)
|
|
255
|
+
- GLOBAL: Shared across all users/tenants
|
|
256
|
+
"""
|
|
257
|
+
USER = "user"
|
|
258
|
+
TENANT = "tenant"
|
|
259
|
+
AGENT = "agent"
|
|
260
|
+
SESSION = "session"
|
|
261
|
+
GLOBAL = "global"
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def valid_scopes(cls) -> List[str]:
|
|
265
|
+
"""Return list of valid scope strings."""
|
|
266
|
+
return [cls.USER, cls.TENANT, cls.AGENT, cls.SESSION, cls.GLOBAL]
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@dataclass
|
|
270
|
+
class MemoryResult:
|
|
271
|
+
"""Result from semantic memory search.
|
|
272
|
+
|
|
273
|
+
Attributes:
|
|
274
|
+
id: Unique identifier for this memory
|
|
275
|
+
content: The original text content that was stored
|
|
276
|
+
score: Similarity score (0.0 to 1.0, higher is more similar)
|
|
277
|
+
metadata: Optional metadata associated with the memory
|
|
278
|
+
"""
|
|
279
|
+
id: str
|
|
280
|
+
content: str
|
|
281
|
+
score: float
|
|
282
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@dataclass
|
|
286
|
+
class MemoryMetadata:
|
|
287
|
+
"""Metadata for storing with a memory.
|
|
288
|
+
|
|
289
|
+
Attributes:
|
|
290
|
+
source: Optional source identifier (e.g., "chat", "document", "api")
|
|
291
|
+
created_at: Optional timestamp string
|
|
292
|
+
extra: Additional key-value metadata
|
|
293
|
+
"""
|
|
294
|
+
source: Optional[str] = None
|
|
295
|
+
created_at: Optional[str] = None
|
|
296
|
+
extra: Dict[str, str] = field(default_factory=dict)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class SemanticMemory:
|
|
300
|
+
"""Vector-backed semantic memory for user/tenant knowledge.
|
|
301
|
+
|
|
302
|
+
Provides semantic search capabilities over stored memories using
|
|
303
|
+
vector embeddings. Memories are automatically embedded and indexed
|
|
304
|
+
for fast similarity search.
|
|
305
|
+
|
|
306
|
+
Requires:
|
|
307
|
+
- OPENAI_API_KEY for embeddings
|
|
308
|
+
- One of: QDRANT_URL, PINECONE_API_KEY+PINECONE_HOST, or POSTGRES_URL for vector storage
|
|
309
|
+
|
|
310
|
+
Example:
|
|
311
|
+
```python
|
|
312
|
+
from agnt5 import SemanticMemory, MemoryScope
|
|
313
|
+
|
|
314
|
+
# Create memory scoped to a user
|
|
315
|
+
memory = SemanticMemory(MemoryScope.USER, "user-123")
|
|
316
|
+
|
|
317
|
+
# Store some memories
|
|
318
|
+
await memory.store("User prefers dark mode")
|
|
319
|
+
await memory.store("User's favorite color is blue")
|
|
320
|
+
|
|
321
|
+
# Search for relevant memories
|
|
322
|
+
results = await memory.search("color preferences")
|
|
323
|
+
for result in results:
|
|
324
|
+
print(f"{result.content} (score: {result.score:.2f})")
|
|
325
|
+
|
|
326
|
+
# Delete a memory
|
|
327
|
+
await memory.forget(results[0].id)
|
|
328
|
+
```
|
|
329
|
+
"""
|
|
330
|
+
|
|
331
|
+
def __init__(self, scope: str, scope_id: str) -> None:
|
|
332
|
+
"""Initialize semantic memory for a scope.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
scope: Memory scope (use MemoryScope constants: USER, TENANT, AGENT, SESSION, GLOBAL)
|
|
336
|
+
scope_id: The unique identifier for the scope (e.g., user_id, tenant_id)
|
|
337
|
+
|
|
338
|
+
Raises:
|
|
339
|
+
ValueError: If scope is not a valid scope string
|
|
340
|
+
"""
|
|
341
|
+
if scope not in MemoryScope.valid_scopes():
|
|
342
|
+
raise ValueError(
|
|
343
|
+
f"Invalid scope '{scope}'. Must be one of: {MemoryScope.valid_scopes()}"
|
|
344
|
+
)
|
|
345
|
+
self.scope = scope
|
|
346
|
+
self.scope_id = scope_id
|
|
347
|
+
self._inner = None # Lazy initialization
|
|
348
|
+
|
|
349
|
+
async def _get_inner(self):
|
|
350
|
+
"""Get or create the underlying Rust SemanticMemory instance."""
|
|
351
|
+
if self._inner is None:
|
|
352
|
+
try:
|
|
353
|
+
from ._core import PySemanticMemory, PyMemoryScope
|
|
354
|
+
except ImportError as e:
|
|
355
|
+
raise ImportError(
|
|
356
|
+
"SemanticMemory requires the agnt5 Rust extension. "
|
|
357
|
+
f"Import error: {e}"
|
|
358
|
+
) from e
|
|
359
|
+
|
|
360
|
+
# Map scope string to PyMemoryScope
|
|
361
|
+
scope_map = {
|
|
362
|
+
MemoryScope.USER: PyMemoryScope.user(),
|
|
363
|
+
MemoryScope.TENANT: PyMemoryScope.tenant(),
|
|
364
|
+
MemoryScope.AGENT: PyMemoryScope.agent(),
|
|
365
|
+
MemoryScope.SESSION: PyMemoryScope.session(),
|
|
366
|
+
MemoryScope.GLOBAL: PyMemoryScope.global_(),
|
|
367
|
+
}
|
|
368
|
+
py_scope = scope_map.get(self.scope)
|
|
369
|
+
if py_scope is None:
|
|
370
|
+
py_scope = PyMemoryScope.from_str(self.scope)
|
|
371
|
+
|
|
372
|
+
self._inner = await PySemanticMemory.from_env(py_scope, self.scope_id)
|
|
373
|
+
return self._inner
|
|
374
|
+
|
|
375
|
+
async def store(self, content: str, metadata: Optional[MemoryMetadata] = None) -> str:
|
|
376
|
+
"""Store content in semantic memory.
|
|
377
|
+
|
|
378
|
+
The content is automatically embedded and indexed for semantic search.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
content: Text content to store
|
|
382
|
+
metadata: Optional metadata to associate with the memory
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
The unique ID of the stored memory
|
|
386
|
+
|
|
387
|
+
Raises:
|
|
388
|
+
RuntimeError: If embedder or vector database is not configured
|
|
389
|
+
"""
|
|
390
|
+
inner = await self._get_inner()
|
|
391
|
+
if metadata is not None:
|
|
392
|
+
from ._core import PyMemoryMetadata
|
|
393
|
+
py_metadata = PyMemoryMetadata(
|
|
394
|
+
source=metadata.source,
|
|
395
|
+
created_at=metadata.created_at,
|
|
396
|
+
extra=metadata.extra,
|
|
397
|
+
)
|
|
398
|
+
return await inner.store_with_metadata(content, py_metadata)
|
|
399
|
+
return await inner.store(content)
|
|
400
|
+
|
|
401
|
+
async def store_batch(
|
|
402
|
+
self,
|
|
403
|
+
contents: List[str],
|
|
404
|
+
metadata: Optional[List[MemoryMetadata]] = None,
|
|
405
|
+
) -> List[str]:
|
|
406
|
+
"""Store multiple contents in batch (more efficient for RAG indexing).
|
|
407
|
+
|
|
408
|
+
Uses batch embedding and batch upsert for better performance when
|
|
409
|
+
indexing many documents.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
contents: List of text contents to store
|
|
413
|
+
metadata: Optional list of metadata (must match contents length)
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
List of unique IDs for all stored memories
|
|
417
|
+
|
|
418
|
+
Raises:
|
|
419
|
+
RuntimeError: If embedder or vector database is not configured
|
|
420
|
+
ValueError: If metadata length doesn't match contents length
|
|
421
|
+
|
|
422
|
+
Example:
|
|
423
|
+
```python
|
|
424
|
+
# Index documents in batch
|
|
425
|
+
docs = ["Doc 1 content...", "Doc 2 content...", "Doc 3 content..."]
|
|
426
|
+
ids = await memory.store_batch(docs)
|
|
427
|
+
|
|
428
|
+
# With metadata for source tracking
|
|
429
|
+
metadata = [
|
|
430
|
+
MemoryMetadata(source="file1.pdf"),
|
|
431
|
+
MemoryMetadata(source="file2.pdf"),
|
|
432
|
+
MemoryMetadata(source="file3.pdf"),
|
|
433
|
+
]
|
|
434
|
+
ids = await memory.store_batch(docs, metadata=metadata)
|
|
435
|
+
```
|
|
436
|
+
"""
|
|
437
|
+
inner = await self._get_inner()
|
|
438
|
+
if metadata is not None:
|
|
439
|
+
if len(metadata) != len(contents):
|
|
440
|
+
raise ValueError(
|
|
441
|
+
f"Metadata length ({len(metadata)}) must match contents length ({len(contents)})"
|
|
442
|
+
)
|
|
443
|
+
from ._core import PyMemoryMetadata
|
|
444
|
+
py_metadata = [
|
|
445
|
+
PyMemoryMetadata(
|
|
446
|
+
source=m.source,
|
|
447
|
+
created_at=m.created_at,
|
|
448
|
+
extra=m.extra,
|
|
449
|
+
)
|
|
450
|
+
for m in metadata
|
|
451
|
+
]
|
|
452
|
+
return await inner.store_batch_with_metadata(contents, py_metadata)
|
|
453
|
+
return await inner.store_batch(contents)
|
|
454
|
+
|
|
455
|
+
async def search(self, query: str, limit: int = 10, min_score: Optional[float] = None) -> List[MemoryResult]:
|
|
456
|
+
"""Search for relevant memories using vector similarity.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
query: Search query text (will be embedded)
|
|
460
|
+
limit: Maximum number of results to return (default: 10)
|
|
461
|
+
min_score: Optional minimum similarity score filter (0.0 to 1.0)
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
List of MemoryResult objects, ranked by similarity score (highest first)
|
|
465
|
+
|
|
466
|
+
Raises:
|
|
467
|
+
RuntimeError: If embedder or vector database is not configured
|
|
468
|
+
"""
|
|
469
|
+
inner = await self._get_inner()
|
|
470
|
+
if min_score is not None:
|
|
471
|
+
results = await inner.search_with_options(query, limit, min_score)
|
|
472
|
+
else:
|
|
473
|
+
results = await inner.search(query, limit)
|
|
474
|
+
|
|
475
|
+
return [
|
|
476
|
+
MemoryResult(
|
|
477
|
+
id=r.id,
|
|
478
|
+
content=r.content,
|
|
479
|
+
score=r.score,
|
|
480
|
+
metadata=dict(r.metadata.extra) if r.metadata else {},
|
|
481
|
+
)
|
|
482
|
+
for r in results
|
|
483
|
+
]
|
|
484
|
+
|
|
485
|
+
async def forget(self, memory_id: str) -> bool:
|
|
486
|
+
"""Delete a memory by its ID.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
memory_id: The unique ID of the memory to delete
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
True if the memory was deleted, False if it wasn't found
|
|
493
|
+
|
|
494
|
+
Raises:
|
|
495
|
+
RuntimeError: If vector database is not configured
|
|
496
|
+
"""
|
|
497
|
+
inner = await self._get_inner()
|
|
498
|
+
return await inner.forget(memory_id)
|
|
499
|
+
|
|
500
|
+
async def get(self, memory_id: str) -> Optional[MemoryResult]:
|
|
501
|
+
"""Get a specific memory by ID.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
memory_id: The unique ID of the memory to retrieve
|
|
505
|
+
|
|
506
|
+
Returns:
|
|
507
|
+
MemoryResult if found, None otherwise
|
|
508
|
+
|
|
509
|
+
Raises:
|
|
510
|
+
RuntimeError: If vector database is not configured
|
|
511
|
+
"""
|
|
512
|
+
inner = await self._get_inner()
|
|
513
|
+
result = await inner.get(memory_id)
|
|
514
|
+
if result is None:
|
|
515
|
+
return None
|
|
516
|
+
return MemoryResult(
|
|
517
|
+
id=result.id,
|
|
518
|
+
content=result.content,
|
|
519
|
+
score=result.score,
|
|
520
|
+
metadata=dict(result.metadata.extra) if result.metadata else {},
|
|
521
|
+
)
|