jaf-py 2.5.9__py3-none-any.whl → 2.5.11__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.
- jaf/__init__.py +154 -57
- jaf/a2a/__init__.py +42 -21
- jaf/a2a/agent.py +79 -126
- jaf/a2a/agent_card.py +87 -78
- jaf/a2a/client.py +30 -66
- jaf/a2a/examples/client_example.py +12 -12
- jaf/a2a/examples/integration_example.py +38 -47
- jaf/a2a/examples/server_example.py +56 -53
- jaf/a2a/memory/__init__.py +0 -4
- jaf/a2a/memory/cleanup.py +28 -21
- jaf/a2a/memory/factory.py +155 -133
- jaf/a2a/memory/providers/composite.py +21 -26
- jaf/a2a/memory/providers/in_memory.py +89 -83
- jaf/a2a/memory/providers/postgres.py +117 -115
- jaf/a2a/memory/providers/redis.py +128 -121
- jaf/a2a/memory/serialization.py +77 -87
- jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
- jaf/a2a/memory/tests/test_cleanup.py +211 -94
- jaf/a2a/memory/tests/test_serialization.py +73 -68
- jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
- jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
- jaf/a2a/memory/types.py +91 -53
- jaf/a2a/protocol.py +95 -125
- jaf/a2a/server.py +90 -118
- jaf/a2a/standalone_client.py +30 -43
- jaf/a2a/tests/__init__.py +16 -33
- jaf/a2a/tests/run_tests.py +17 -53
- jaf/a2a/tests/test_agent.py +40 -140
- jaf/a2a/tests/test_client.py +54 -117
- jaf/a2a/tests/test_integration.py +28 -82
- jaf/a2a/tests/test_protocol.py +54 -139
- jaf/a2a/tests/test_types.py +50 -136
- jaf/a2a/types.py +58 -34
- jaf/cli.py +21 -41
- jaf/core/__init__.py +7 -1
- jaf/core/agent_tool.py +93 -72
- jaf/core/analytics.py +257 -207
- jaf/core/checkpoint.py +223 -0
- jaf/core/composition.py +249 -235
- jaf/core/engine.py +817 -519
- jaf/core/errors.py +55 -42
- jaf/core/guardrails.py +276 -202
- jaf/core/handoff.py +47 -31
- jaf/core/parallel_agents.py +69 -75
- jaf/core/performance.py +75 -73
- jaf/core/proxy.py +43 -44
- jaf/core/proxy_helpers.py +24 -27
- jaf/core/regeneration.py +220 -129
- jaf/core/state.py +68 -66
- jaf/core/streaming.py +115 -108
- jaf/core/tool_results.py +111 -101
- jaf/core/tools.py +114 -116
- jaf/core/tracing.py +269 -210
- jaf/core/types.py +371 -151
- jaf/core/workflows.py +209 -168
- jaf/exceptions.py +46 -38
- jaf/memory/__init__.py +1 -6
- jaf/memory/approval_storage.py +54 -77
- jaf/memory/factory.py +4 -4
- jaf/memory/providers/in_memory.py +216 -180
- jaf/memory/providers/postgres.py +216 -146
- jaf/memory/providers/redis.py +173 -116
- jaf/memory/types.py +70 -51
- jaf/memory/utils.py +36 -34
- jaf/plugins/__init__.py +12 -12
- jaf/plugins/base.py +105 -96
- jaf/policies/__init__.py +0 -1
- jaf/policies/handoff.py +37 -46
- jaf/policies/validation.py +76 -52
- jaf/providers/__init__.py +6 -3
- jaf/providers/mcp.py +97 -51
- jaf/providers/model.py +361 -280
- jaf/server/__init__.py +1 -1
- jaf/server/main.py +7 -11
- jaf/server/server.py +514 -359
- jaf/server/types.py +208 -52
- jaf/utils/__init__.py +17 -18
- jaf/utils/attachments.py +111 -116
- jaf/utils/document_processor.py +175 -174
- jaf/visualization/__init__.py +1 -1
- jaf/visualization/example.py +111 -110
- jaf/visualization/functional_core.py +46 -71
- jaf/visualization/graphviz.py +154 -189
- jaf/visualization/imperative_shell.py +7 -16
- jaf/visualization/types.py +8 -4
- {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
- jaf_py-2.5.11.dist-info/RECORD +97 -0
- jaf_py-2.5.9.dist-info/RECORD +0 -96
- {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
|
@@ -29,7 +29,7 @@ from ..types import (
|
|
|
29
29
|
class InMemoryProvider(MemoryProvider):
|
|
30
30
|
"""
|
|
31
31
|
In-memory implementation of MemoryProvider.
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
Uses OrderedDict for LRU behavior and enforces memory limits through
|
|
34
34
|
automatic eviction of oldest conversations.
|
|
35
35
|
"""
|
|
@@ -39,7 +39,9 @@ class InMemoryProvider(MemoryProvider):
|
|
|
39
39
|
self._conversations: OrderedDict[str, ConversationMemory] = OrderedDict()
|
|
40
40
|
self._lock = asyncio.Lock()
|
|
41
41
|
|
|
42
|
-
print(
|
|
42
|
+
print(
|
|
43
|
+
f"[MEMORY:InMemory] Initialized with max {config.max_conversations} conversations, {config.max_messages_per_conversation} messages each"
|
|
44
|
+
)
|
|
43
45
|
|
|
44
46
|
async def _enforce_memory_limits(self) -> None:
|
|
45
47
|
"""Enforce conversation and message limits through LRU eviction."""
|
|
@@ -51,48 +53,51 @@ class InMemoryProvider(MemoryProvider):
|
|
|
51
53
|
self,
|
|
52
54
|
conversation_id: str,
|
|
53
55
|
messages: List[Message],
|
|
54
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
56
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
55
57
|
) -> Result[None, MemoryStorageError]:
|
|
56
58
|
"""Store messages for a conversation."""
|
|
57
59
|
async with self._lock:
|
|
58
60
|
try:
|
|
59
61
|
now = datetime.now()
|
|
60
62
|
|
|
61
|
-
limited_messages = messages[-self.config.max_messages_per_conversation:]
|
|
63
|
+
limited_messages = messages[-self.config.max_messages_per_conversation :]
|
|
62
64
|
|
|
63
65
|
conversation_metadata = {
|
|
64
66
|
"created_at": now,
|
|
65
67
|
"updated_at": now,
|
|
66
68
|
"total_messages": len(limited_messages),
|
|
67
69
|
"last_activity": now,
|
|
68
|
-
**(metadata or {})
|
|
70
|
+
**(metadata or {}),
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
conversation = ConversationMemory(
|
|
72
74
|
conversation_id=conversation_id,
|
|
73
75
|
user_id=metadata.get("user_id") if metadata else None,
|
|
74
76
|
messages=limited_messages,
|
|
75
|
-
metadata=conversation_metadata
|
|
77
|
+
metadata=conversation_metadata,
|
|
76
78
|
)
|
|
77
79
|
|
|
78
80
|
self._conversations[conversation_id] = conversation
|
|
79
81
|
self._conversations.move_to_end(conversation_id)
|
|
80
82
|
await self._enforce_memory_limits()
|
|
81
83
|
|
|
82
|
-
print(
|
|
84
|
+
print(
|
|
85
|
+
f"[MEMORY:InMemory] Stored {len(limited_messages)} messages for conversation {conversation_id}"
|
|
86
|
+
)
|
|
83
87
|
return Success(None)
|
|
84
88
|
|
|
85
89
|
except Exception as e:
|
|
86
|
-
return Failure(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
return Failure(
|
|
91
|
+
MemoryStorageError(
|
|
92
|
+
operation="store_messages",
|
|
93
|
+
provider="InMemory",
|
|
94
|
+
message=f"Failed to store messages: {e}",
|
|
95
|
+
cause=e,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
92
98
|
|
|
93
99
|
async def get_conversation(
|
|
94
|
-
self,
|
|
95
|
-
conversation_id: str
|
|
100
|
+
self, conversation_id: str
|
|
96
101
|
) -> Result[Optional[ConversationMemory], MemoryStorageError]:
|
|
97
102
|
"""Retrieve conversation history."""
|
|
98
103
|
async with self._lock:
|
|
@@ -108,65 +113,74 @@ class InMemoryProvider(MemoryProvider):
|
|
|
108
113
|
return Success(conversation)
|
|
109
114
|
|
|
110
115
|
except Exception as e:
|
|
111
|
-
return Failure(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
return Failure(
|
|
117
|
+
MemoryStorageError(
|
|
118
|
+
message=f"Error retrieving conversation: {e}",
|
|
119
|
+
provider="InMemory",
|
|
120
|
+
operation="get_conversation",
|
|
121
|
+
cause=e,
|
|
122
|
+
)
|
|
123
|
+
)
|
|
117
124
|
|
|
118
125
|
async def append_messages(
|
|
119
126
|
self,
|
|
120
127
|
conversation_id: str,
|
|
121
128
|
messages: List[Message],
|
|
122
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
129
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
123
130
|
) -> Result[None, Union[MemoryNotFoundError, MemoryStorageError]]:
|
|
124
131
|
"""Append new messages to existing conversation."""
|
|
125
132
|
async with self._lock:
|
|
126
133
|
try:
|
|
127
134
|
existing = self._conversations.get(conversation_id)
|
|
128
135
|
if existing is None:
|
|
129
|
-
return Failure(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
return Failure(
|
|
137
|
+
MemoryNotFoundError(
|
|
138
|
+
message=f"Conversation {conversation_id} not found",
|
|
139
|
+
provider="InMemory",
|
|
140
|
+
conversation_id=conversation_id,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
134
143
|
|
|
135
144
|
combined_messages = list(existing.messages) + messages
|
|
136
|
-
limited_messages = combined_messages[-self.config.max_messages_per_conversation:]
|
|
145
|
+
limited_messages = combined_messages[-self.config.max_messages_per_conversation :]
|
|
137
146
|
|
|
138
147
|
now = datetime.now()
|
|
139
|
-
existing.metadata.update(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
existing.metadata.update(
|
|
149
|
+
{
|
|
150
|
+
"updated_at": now,
|
|
151
|
+
"last_activity": now,
|
|
152
|
+
"total_messages": len(limited_messages),
|
|
153
|
+
**(metadata or {}),
|
|
154
|
+
}
|
|
155
|
+
)
|
|
145
156
|
|
|
146
157
|
updated_conversation = ConversationMemory(
|
|
147
158
|
conversation_id=conversation_id,
|
|
148
159
|
user_id=existing.user_id,
|
|
149
160
|
messages=limited_messages,
|
|
150
|
-
metadata=existing.metadata
|
|
161
|
+
metadata=existing.metadata,
|
|
151
162
|
)
|
|
152
163
|
|
|
153
164
|
self._conversations[conversation_id] = updated_conversation
|
|
154
165
|
self._conversations.move_to_end(conversation_id)
|
|
155
166
|
|
|
156
|
-
print(
|
|
167
|
+
print(
|
|
168
|
+
f"[MEMORY:InMemory] Appended {len(messages)} messages to conversation {conversation_id}"
|
|
169
|
+
)
|
|
157
170
|
return Success(None)
|
|
158
171
|
|
|
159
172
|
except Exception as e:
|
|
160
|
-
return Failure(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
173
|
+
return Failure(
|
|
174
|
+
MemoryStorageError(
|
|
175
|
+
message=f"Failed to append messages: {e}",
|
|
176
|
+
provider="InMemory",
|
|
177
|
+
operation="append_messages",
|
|
178
|
+
cause=e,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
166
181
|
|
|
167
182
|
async def find_conversations(
|
|
168
|
-
self,
|
|
169
|
-
query: MemoryQuery
|
|
183
|
+
self, query: MemoryQuery
|
|
170
184
|
) -> Result[List[ConversationMemory], MemoryStorageError]:
|
|
171
185
|
"""Search conversations by query parameters."""
|
|
172
186
|
async with self._lock:
|
|
@@ -185,26 +199,28 @@ class InMemoryProvider(MemoryProvider):
|
|
|
185
199
|
continue
|
|
186
200
|
results.append(conv)
|
|
187
201
|
|
|
188
|
-
results.sort(
|
|
202
|
+
results.sort(
|
|
203
|
+
key=lambda c: c.metadata.get("last_activity", datetime.min), reverse=True
|
|
204
|
+
)
|
|
189
205
|
|
|
190
206
|
offset = query.offset or 0
|
|
191
207
|
limit = query.limit or len(results)
|
|
192
|
-
paginated_results = results[offset:offset + limit]
|
|
208
|
+
paginated_results = results[offset : offset + limit]
|
|
193
209
|
|
|
194
210
|
return Success(paginated_results)
|
|
195
211
|
|
|
196
212
|
except Exception as e:
|
|
197
|
-
return Failure(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
213
|
+
return Failure(
|
|
214
|
+
MemoryStorageError(
|
|
215
|
+
message=f"Error finding conversations: {e}",
|
|
216
|
+
provider="InMemory",
|
|
217
|
+
operation="find_conversations",
|
|
218
|
+
cause=e,
|
|
219
|
+
)
|
|
220
|
+
)
|
|
203
221
|
|
|
204
222
|
async def get_recent_messages(
|
|
205
|
-
self,
|
|
206
|
-
conversation_id: str,
|
|
207
|
-
limit: int = 50
|
|
223
|
+
self, conversation_id: str, limit: int = 50
|
|
208
224
|
) -> Result[List[Message], Union[MemoryNotFoundError, MemoryStorageError]]:
|
|
209
225
|
"""Get recent messages from a conversation."""
|
|
210
226
|
result = await self.get_conversation(conversation_id)
|
|
@@ -213,18 +229,17 @@ class InMemoryProvider(MemoryProvider):
|
|
|
213
229
|
|
|
214
230
|
conversation = result.data
|
|
215
231
|
if not conversation:
|
|
216
|
-
return Failure(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
232
|
+
return Failure(
|
|
233
|
+
MemoryNotFoundError(
|
|
234
|
+
message=f"Conversation {conversation_id} not found",
|
|
235
|
+
provider="InMemory",
|
|
236
|
+
conversation_id=conversation_id,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
221
239
|
|
|
222
240
|
return Success(list(conversation.messages[-limit:]))
|
|
223
241
|
|
|
224
|
-
async def delete_conversation(
|
|
225
|
-
self,
|
|
226
|
-
conversation_id: str
|
|
227
|
-
) -> Result[bool, MemoryStorageError]:
|
|
242
|
+
async def delete_conversation(self, conversation_id: str) -> Result[bool, MemoryStorageError]:
|
|
228
243
|
"""Delete conversation and return True if it existed."""
|
|
229
244
|
async with self._lock:
|
|
230
245
|
try:
|
|
@@ -233,84 +248,91 @@ class InMemoryProvider(MemoryProvider):
|
|
|
233
248
|
del self._conversations[conversation_id]
|
|
234
249
|
return Success(existed)
|
|
235
250
|
except Exception as e:
|
|
236
|
-
return Failure(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
251
|
+
return Failure(
|
|
252
|
+
MemoryStorageError(
|
|
253
|
+
message=f"Error deleting conversation: {e}",
|
|
254
|
+
provider="InMemory",
|
|
255
|
+
operation="delete_conversation",
|
|
256
|
+
cause=e,
|
|
257
|
+
)
|
|
258
|
+
)
|
|
242
259
|
|
|
243
|
-
async def clear_user_conversations(
|
|
244
|
-
self,
|
|
245
|
-
user_id: str
|
|
246
|
-
) -> Result[int, MemoryStorageError]:
|
|
260
|
+
async def clear_user_conversations(self, user_id: str) -> Result[int, MemoryStorageError]:
|
|
247
261
|
"""Clear all conversations for a user and return count deleted."""
|
|
248
262
|
async with self._lock:
|
|
249
263
|
try:
|
|
250
264
|
to_delete = [
|
|
251
|
-
conv_id
|
|
265
|
+
conv_id
|
|
266
|
+
for conv_id, conv in self._conversations.items()
|
|
252
267
|
if conv.user_id == user_id
|
|
253
268
|
]
|
|
254
269
|
for conv_id in to_delete:
|
|
255
270
|
del self._conversations[conv_id]
|
|
256
271
|
return Success(len(to_delete))
|
|
257
272
|
except Exception as e:
|
|
258
|
-
return Failure(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
return Failure(
|
|
274
|
+
MemoryStorageError(
|
|
275
|
+
message=f"Error clearing conversations: {e}",
|
|
276
|
+
provider="InMemory",
|
|
277
|
+
operation="clear_user_conversations",
|
|
278
|
+
cause=e,
|
|
279
|
+
)
|
|
280
|
+
)
|
|
264
281
|
|
|
265
282
|
async def get_stats(
|
|
266
|
-
self,
|
|
267
|
-
user_id: Optional[str] = None
|
|
283
|
+
self, user_id: Optional[str] = None
|
|
268
284
|
) -> Result[Dict[str, Any], MemoryStorageError]:
|
|
269
285
|
"""Get conversation statistics."""
|
|
270
286
|
async with self._lock:
|
|
271
287
|
try:
|
|
272
|
-
convs = [
|
|
288
|
+
convs = [
|
|
289
|
+
c for c in self._conversations.values() if not user_id or c.user_id == user_id
|
|
290
|
+
]
|
|
273
291
|
total_messages = sum(len(c.messages) for c in convs)
|
|
274
|
-
created_dates = [
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
292
|
+
created_dates = [
|
|
293
|
+
c.metadata["created_at"] for c in convs if "created_at" in c.metadata
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
return Success(
|
|
297
|
+
{
|
|
298
|
+
"total_conversations": len(convs),
|
|
299
|
+
"total_messages": total_messages,
|
|
300
|
+
"oldest_conversation": min(created_dates) if created_dates else None,
|
|
301
|
+
"newest_conversation": max(created_dates) if created_dates else None,
|
|
302
|
+
}
|
|
303
|
+
)
|
|
282
304
|
except Exception as e:
|
|
283
|
-
return Failure(
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
305
|
+
return Failure(
|
|
306
|
+
MemoryStorageError(
|
|
307
|
+
message=f"Error getting stats: {e}",
|
|
308
|
+
provider="InMemory",
|
|
309
|
+
operation="get_stats",
|
|
310
|
+
cause=e,
|
|
311
|
+
)
|
|
312
|
+
)
|
|
289
313
|
|
|
290
314
|
async def health_check(self) -> Result[Dict[str, Any], MemoryConnectionError]:
|
|
291
315
|
"""Check provider health and return status information."""
|
|
292
316
|
start_time = datetime.now()
|
|
293
317
|
try:
|
|
294
318
|
latency_ms = (datetime.now() - start_time).total_seconds() * 1000
|
|
295
|
-
return Success(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"conversations_count": len(self._conversations)
|
|
319
|
+
return Success(
|
|
320
|
+
{
|
|
321
|
+
"healthy": True,
|
|
322
|
+
"latency_ms": latency_ms,
|
|
323
|
+
"provider": "InMemory",
|
|
324
|
+
"details": {"conversations_count": len(self._conversations)},
|
|
301
325
|
}
|
|
302
|
-
|
|
326
|
+
)
|
|
303
327
|
except Exception as e:
|
|
304
|
-
return Failure(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
)
|
|
328
|
+
return Failure(
|
|
329
|
+
MemoryConnectionError(
|
|
330
|
+
message=f"Health check failed: {e}", provider="InMemory", cause=e
|
|
331
|
+
)
|
|
332
|
+
)
|
|
309
333
|
|
|
310
334
|
async def truncate_conversation_after(
|
|
311
|
-
self,
|
|
312
|
-
conversation_id: str,
|
|
313
|
-
message_id: MessageId
|
|
335
|
+
self, conversation_id: str, message_id: MessageId
|
|
314
336
|
) -> Result[int, Union[MemoryNotFoundError, MemoryStorageError]]:
|
|
315
337
|
"""
|
|
316
338
|
Truncate conversation after (and including) the specified message ID.
|
|
@@ -320,24 +342,26 @@ class InMemoryProvider(MemoryProvider):
|
|
|
320
342
|
try:
|
|
321
343
|
conversation = self._conversations.get(conversation_id)
|
|
322
344
|
if conversation is None:
|
|
323
|
-
return Failure(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
345
|
+
return Failure(
|
|
346
|
+
MemoryNotFoundError(
|
|
347
|
+
message=f"Conversation {conversation_id} not found",
|
|
348
|
+
provider="InMemory",
|
|
349
|
+
conversation_id=conversation_id,
|
|
350
|
+
)
|
|
351
|
+
)
|
|
328
352
|
|
|
329
353
|
messages = list(conversation.messages)
|
|
330
354
|
truncate_index = find_message_index(messages, message_id)
|
|
331
|
-
|
|
355
|
+
|
|
332
356
|
if truncate_index is None:
|
|
333
357
|
# Message not found, nothing to truncate
|
|
334
358
|
return Success(0)
|
|
335
|
-
|
|
359
|
+
|
|
336
360
|
# Truncate messages from the found index onwards
|
|
337
361
|
original_count = len(messages)
|
|
338
362
|
truncated_messages = messages[:truncate_index]
|
|
339
363
|
removed_count = original_count - len(truncated_messages)
|
|
340
|
-
|
|
364
|
+
|
|
341
365
|
# Update conversation with truncated messages
|
|
342
366
|
now = datetime.now()
|
|
343
367
|
updated_metadata = {
|
|
@@ -347,34 +371,36 @@ class InMemoryProvider(MemoryProvider):
|
|
|
347
371
|
"total_messages": len(truncated_messages),
|
|
348
372
|
"regeneration_truncated": True,
|
|
349
373
|
"truncated_at": now.isoformat(),
|
|
350
|
-
"messages_removed": removed_count
|
|
374
|
+
"messages_removed": removed_count,
|
|
351
375
|
}
|
|
352
|
-
|
|
376
|
+
|
|
353
377
|
updated_conversation = ConversationMemory(
|
|
354
378
|
conversation_id=conversation_id,
|
|
355
379
|
user_id=conversation.user_id,
|
|
356
380
|
messages=truncated_messages,
|
|
357
|
-
metadata=updated_metadata
|
|
381
|
+
metadata=updated_metadata,
|
|
358
382
|
)
|
|
359
|
-
|
|
383
|
+
|
|
360
384
|
self._conversations[conversation_id] = updated_conversation
|
|
361
385
|
self._conversations.move_to_end(conversation_id)
|
|
362
|
-
|
|
363
|
-
print(
|
|
386
|
+
|
|
387
|
+
print(
|
|
388
|
+
f"[MEMORY:InMemory] Truncated conversation {conversation_id}: removed {removed_count} messages after message {message_id}"
|
|
389
|
+
)
|
|
364
390
|
return Success(removed_count)
|
|
365
|
-
|
|
391
|
+
|
|
366
392
|
except Exception as e:
|
|
367
|
-
return Failure(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
393
|
+
return Failure(
|
|
394
|
+
MemoryStorageError(
|
|
395
|
+
message=f"Failed to truncate conversation: {e}",
|
|
396
|
+
provider="InMemory",
|
|
397
|
+
operation="truncate_conversation_after",
|
|
398
|
+
cause=e,
|
|
399
|
+
)
|
|
400
|
+
)
|
|
373
401
|
|
|
374
402
|
async def get_conversation_until_message(
|
|
375
|
-
self,
|
|
376
|
-
conversation_id: str,
|
|
377
|
-
message_id: MessageId
|
|
403
|
+
self, conversation_id: str, message_id: MessageId
|
|
378
404
|
) -> Result[Optional[ConversationMemory], Union[MemoryNotFoundError, MemoryStorageError]]:
|
|
379
405
|
"""
|
|
380
406
|
Get conversation history up to (but not including) the specified message ID.
|
|
@@ -385,18 +411,20 @@ class InMemoryProvider(MemoryProvider):
|
|
|
385
411
|
conversation = self._conversations.get(conversation_id)
|
|
386
412
|
if conversation is None:
|
|
387
413
|
return Success(None)
|
|
388
|
-
|
|
414
|
+
|
|
389
415
|
messages = list(conversation.messages)
|
|
390
416
|
until_index = find_message_index(messages, message_id)
|
|
391
|
-
|
|
417
|
+
|
|
392
418
|
if until_index is None:
|
|
393
419
|
# Message not found, return None as lightweight indicator
|
|
394
|
-
print(
|
|
420
|
+
print(
|
|
421
|
+
f"[MEMORY:InMemory] Message {message_id} not found in conversation {conversation_id}"
|
|
422
|
+
)
|
|
395
423
|
return Success(None)
|
|
396
|
-
|
|
424
|
+
|
|
397
425
|
# Return conversation up to (but not including) the specified message
|
|
398
426
|
truncated_messages = messages[:until_index]
|
|
399
|
-
|
|
427
|
+
|
|
400
428
|
# Create a copy of the conversation with truncated messages
|
|
401
429
|
truncated_conversation = ConversationMemory(
|
|
402
430
|
conversation_id=conversation.conversation_id,
|
|
@@ -407,26 +435,27 @@ class InMemoryProvider(MemoryProvider):
|
|
|
407
435
|
"truncated_for_regeneration": True,
|
|
408
436
|
"truncated_until_message": str(message_id),
|
|
409
437
|
"original_message_count": len(messages),
|
|
410
|
-
"truncated_message_count": len(truncated_messages)
|
|
411
|
-
}
|
|
438
|
+
"truncated_message_count": len(truncated_messages),
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
print(
|
|
443
|
+
f"[MEMORY:InMemory] Retrieved conversation {conversation_id} until message {message_id}: {len(truncated_messages)} messages (found at index {until_index})"
|
|
412
444
|
)
|
|
413
|
-
|
|
414
|
-
print(f"[MEMORY:InMemory] Retrieved conversation {conversation_id} until message {message_id}: {len(truncated_messages)} messages (found at index {until_index})")
|
|
415
445
|
return Success(truncated_conversation)
|
|
416
|
-
|
|
446
|
+
|
|
417
447
|
except Exception as e:
|
|
418
|
-
return Failure(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
448
|
+
return Failure(
|
|
449
|
+
MemoryStorageError(
|
|
450
|
+
message=f"Failed to get conversation until message: {e}",
|
|
451
|
+
provider="InMemory",
|
|
452
|
+
operation="get_conversation_until_message",
|
|
453
|
+
cause=e,
|
|
454
|
+
)
|
|
455
|
+
)
|
|
424
456
|
|
|
425
457
|
async def mark_regeneration_point(
|
|
426
|
-
self,
|
|
427
|
-
conversation_id: str,
|
|
428
|
-
message_id: MessageId,
|
|
429
|
-
regeneration_metadata: Dict[str, Any]
|
|
458
|
+
self, conversation_id: str, message_id: MessageId, regeneration_metadata: Dict[str, Any]
|
|
430
459
|
) -> Result[None, Union[MemoryNotFoundError, MemoryStorageError]]:
|
|
431
460
|
"""
|
|
432
461
|
Mark a regeneration point in the conversation for audit purposes.
|
|
@@ -435,50 +464,56 @@ class InMemoryProvider(MemoryProvider):
|
|
|
435
464
|
try:
|
|
436
465
|
conversation = self._conversations.get(conversation_id)
|
|
437
466
|
if conversation is None:
|
|
438
|
-
return Failure(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
467
|
+
return Failure(
|
|
468
|
+
MemoryNotFoundError(
|
|
469
|
+
message=f"Conversation {conversation_id} not found",
|
|
470
|
+
provider="InMemory",
|
|
471
|
+
conversation_id=conversation_id,
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
|
|
444
475
|
# Add regeneration point to metadata
|
|
445
476
|
regeneration_points = conversation.metadata.get("regeneration_points", [])
|
|
446
477
|
regeneration_point = {
|
|
447
478
|
"message_id": str(message_id),
|
|
448
479
|
"timestamp": datetime.now().isoformat(),
|
|
449
|
-
**regeneration_metadata
|
|
480
|
+
**regeneration_metadata,
|
|
450
481
|
}
|
|
451
482
|
regeneration_points.append(regeneration_point)
|
|
452
|
-
|
|
483
|
+
|
|
453
484
|
# Update conversation metadata
|
|
454
485
|
updated_metadata = {
|
|
455
486
|
**conversation.metadata,
|
|
456
487
|
"regeneration_points": regeneration_points,
|
|
457
488
|
"last_regeneration": regeneration_point,
|
|
458
489
|
"updated_at": datetime.now(),
|
|
459
|
-
"regeneration_count": len(regeneration_points)
|
|
490
|
+
"regeneration_count": len(regeneration_points),
|
|
460
491
|
}
|
|
461
|
-
|
|
492
|
+
|
|
462
493
|
updated_conversation = ConversationMemory(
|
|
463
494
|
conversation_id=conversation.conversation_id,
|
|
464
495
|
user_id=conversation.user_id,
|
|
465
496
|
messages=conversation.messages,
|
|
466
|
-
metadata=updated_metadata
|
|
497
|
+
metadata=updated_metadata,
|
|
467
498
|
)
|
|
468
|
-
|
|
499
|
+
|
|
469
500
|
self._conversations[conversation_id] = updated_conversation
|
|
470
501
|
self._conversations.move_to_end(conversation_id)
|
|
471
|
-
|
|
472
|
-
print(
|
|
502
|
+
|
|
503
|
+
print(
|
|
504
|
+
f"[MEMORY:InMemory] Marked regeneration point for conversation {conversation_id} at message {message_id}"
|
|
505
|
+
)
|
|
473
506
|
return Success(None)
|
|
474
|
-
|
|
507
|
+
|
|
475
508
|
except Exception as e:
|
|
476
|
-
return Failure(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
509
|
+
return Failure(
|
|
510
|
+
MemoryStorageError(
|
|
511
|
+
message=f"Failed to mark regeneration point: {e}",
|
|
512
|
+
provider="InMemory",
|
|
513
|
+
operation="mark_regeneration_point",
|
|
514
|
+
cause=e,
|
|
515
|
+
)
|
|
516
|
+
)
|
|
482
517
|
|
|
483
518
|
async def close(self) -> Result[None, MemoryConnectionError]:
|
|
484
519
|
"""Close/cleanup the provider."""
|
|
@@ -487,13 +522,14 @@ class InMemoryProvider(MemoryProvider):
|
|
|
487
522
|
print("[MEMORY:InMemory] Closed provider, cleared all conversations")
|
|
488
523
|
return Success(None)
|
|
489
524
|
|
|
525
|
+
|
|
490
526
|
def create_in_memory_provider(config: Optional[InMemoryConfig] = None) -> InMemoryProvider:
|
|
491
527
|
"""
|
|
492
528
|
Factory function to create an in-memory provider instance.
|
|
493
|
-
|
|
529
|
+
|
|
494
530
|
Args:
|
|
495
531
|
config: Configuration for the provider. If None, uses defaults.
|
|
496
|
-
|
|
532
|
+
|
|
497
533
|
Returns:
|
|
498
534
|
Configured InMemoryProvider instance.
|
|
499
535
|
"""
|