jaf-py 2.5.10__py3-none-any.whl → 2.5.12__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 +310 -210
- jaf/core/types.py +403 -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 +475 -283
- 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.10.dist-info → jaf_py-2.5.12.dist-info}/METADATA +2 -2
- jaf_py-2.5.12.dist-info/RECORD +97 -0
- jaf_py-2.5.10.dist-info/RECORD +0 -96
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
jaf/memory/types.py
CHANGED
|
@@ -14,29 +14,36 @@ from pydantic import BaseModel, Field
|
|
|
14
14
|
from ..core.types import Message, TraceId, MessageId
|
|
15
15
|
|
|
16
16
|
# Generic Result type for functional error handling
|
|
17
|
-
T = TypeVar(
|
|
18
|
-
E = TypeVar(
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
E = TypeVar("E", bound="MemoryError")
|
|
19
|
+
|
|
19
20
|
|
|
20
21
|
@dataclass(frozen=True)
|
|
21
22
|
class Success(Generic[T]):
|
|
22
23
|
"""Represents a successful operation result."""
|
|
24
|
+
|
|
23
25
|
data: T
|
|
24
26
|
|
|
27
|
+
|
|
25
28
|
@dataclass(frozen=True)
|
|
26
29
|
class Failure(Generic[E]):
|
|
27
30
|
"""Represents a failed operation result."""
|
|
31
|
+
|
|
28
32
|
error: E
|
|
29
33
|
|
|
34
|
+
|
|
30
35
|
Result = Union[Success[T], Failure[E]]
|
|
31
36
|
|
|
37
|
+
|
|
32
38
|
@dataclass(frozen=True)
|
|
33
39
|
class ConversationMemory:
|
|
34
40
|
"""
|
|
35
41
|
Immutable conversation memory object containing conversation history and metadata.
|
|
36
|
-
|
|
42
|
+
|
|
37
43
|
This represents a complete conversation stored in memory, including all messages
|
|
38
44
|
and associated metadata like creation time, user information, and trace data.
|
|
39
45
|
"""
|
|
46
|
+
|
|
40
47
|
conversation_id: str
|
|
41
48
|
user_id: Optional[str] = None
|
|
42
49
|
messages: List[Message] = field(default_factory=list)
|
|
@@ -45,11 +52,13 @@ class ConversationMemory:
|
|
|
45
52
|
def __post_init__(self):
|
|
46
53
|
"""Ensure messages list is frozen (immutable)."""
|
|
47
54
|
if self.messages is not None:
|
|
48
|
-
object.__setattr__(self,
|
|
55
|
+
object.__setattr__(self, "messages", tuple(self.messages))
|
|
56
|
+
|
|
49
57
|
|
|
50
58
|
@dataclass(frozen=True)
|
|
51
59
|
class MemoryQuery:
|
|
52
60
|
"""Query parameters for searching conversations in memory providers."""
|
|
61
|
+
|
|
53
62
|
conversation_id: Optional[str] = None
|
|
54
63
|
user_id: Optional[str] = None
|
|
55
64
|
trace_id: Optional[TraceId] = None
|
|
@@ -58,10 +67,11 @@ class MemoryQuery:
|
|
|
58
67
|
since: Optional[datetime] = None
|
|
59
68
|
until: Optional[datetime] = None
|
|
60
69
|
|
|
70
|
+
|
|
61
71
|
class MemoryProvider(Protocol):
|
|
62
72
|
"""
|
|
63
73
|
Protocol defining the interface that all memory providers must implement.
|
|
64
|
-
|
|
74
|
+
|
|
65
75
|
This protocol ensures consistent behavior across different storage backends
|
|
66
76
|
(in-memory, Redis, PostgreSQL) while maintaining type safety.
|
|
67
77
|
"""
|
|
@@ -70,15 +80,14 @@ class MemoryProvider(Protocol):
|
|
|
70
80
|
self,
|
|
71
81
|
conversation_id: str,
|
|
72
82
|
messages: List[Message],
|
|
73
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
74
|
-
) -> Result[None,
|
|
83
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
84
|
+
) -> Result[None, "MemoryStorageError"]:
|
|
75
85
|
"""Store messages for a conversation."""
|
|
76
86
|
...
|
|
77
87
|
|
|
78
88
|
async def get_conversation(
|
|
79
|
-
self,
|
|
80
|
-
|
|
81
|
-
) -> Result[Optional[ConversationMemory], 'MemoryStorageError']:
|
|
89
|
+
self, conversation_id: str
|
|
90
|
+
) -> Result[Optional[ConversationMemory], "MemoryStorageError"]:
|
|
82
91
|
"""Retrieve conversation history."""
|
|
83
92
|
...
|
|
84
93
|
|
|
@@ -86,61 +95,49 @@ class MemoryProvider(Protocol):
|
|
|
86
95
|
self,
|
|
87
96
|
conversation_id: str,
|
|
88
97
|
messages: List[Message],
|
|
89
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
90
|
-
) -> Result[None, Union[
|
|
98
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
99
|
+
) -> Result[None, Union["MemoryNotFoundError", "MemoryStorageError"]]:
|
|
91
100
|
"""Append new messages to existing conversation."""
|
|
92
101
|
...
|
|
93
102
|
|
|
94
103
|
async def find_conversations(
|
|
95
|
-
self,
|
|
96
|
-
|
|
97
|
-
) -> Result[List[ConversationMemory], 'MemoryStorageError']:
|
|
104
|
+
self, query: MemoryQuery
|
|
105
|
+
) -> Result[List[ConversationMemory], "MemoryStorageError"]:
|
|
98
106
|
"""Search conversations by query parameters."""
|
|
99
107
|
...
|
|
100
108
|
|
|
101
109
|
async def get_recent_messages(
|
|
102
|
-
self,
|
|
103
|
-
|
|
104
|
-
limit: int = 50
|
|
105
|
-
) -> Result[List[Message], Union['MemoryNotFoundError', 'MemoryStorageError']]:
|
|
110
|
+
self, conversation_id: str, limit: int = 50
|
|
111
|
+
) -> Result[List[Message], Union["MemoryNotFoundError", "MemoryStorageError"]]:
|
|
106
112
|
"""Get recent messages from a conversation."""
|
|
107
113
|
...
|
|
108
114
|
|
|
109
|
-
async def delete_conversation(
|
|
110
|
-
self,
|
|
111
|
-
conversation_id: str
|
|
112
|
-
) -> Result[bool, 'MemoryStorageError']:
|
|
115
|
+
async def delete_conversation(self, conversation_id: str) -> Result[bool, "MemoryStorageError"]:
|
|
113
116
|
"""Delete conversation and return True if it existed."""
|
|
114
117
|
...
|
|
115
118
|
|
|
116
|
-
async def clear_user_conversations(
|
|
117
|
-
self,
|
|
118
|
-
user_id: str
|
|
119
|
-
) -> Result[int, 'MemoryStorageError']:
|
|
119
|
+
async def clear_user_conversations(self, user_id: str) -> Result[int, "MemoryStorageError"]:
|
|
120
120
|
"""Clear all conversations for a user and return count deleted."""
|
|
121
121
|
...
|
|
122
122
|
|
|
123
123
|
async def get_stats(
|
|
124
|
-
self,
|
|
125
|
-
|
|
126
|
-
) -> Result[Dict[str, Any], 'MemoryStorageError']:
|
|
124
|
+
self, user_id: Optional[str] = None
|
|
125
|
+
) -> Result[Dict[str, Any], "MemoryStorageError"]:
|
|
127
126
|
"""Get conversation statistics."""
|
|
128
127
|
...
|
|
129
128
|
|
|
130
|
-
async def health_check(self) -> Result[Dict[str, Any],
|
|
129
|
+
async def health_check(self) -> Result[Dict[str, Any], "MemoryConnectionError"]:
|
|
131
130
|
"""Check provider health and return status information."""
|
|
132
131
|
...
|
|
133
132
|
|
|
134
|
-
async def close(self) -> Result[None,
|
|
133
|
+
async def close(self) -> Result[None, "MemoryConnectionError"]:
|
|
135
134
|
"""Close/cleanup the provider."""
|
|
136
135
|
...
|
|
137
136
|
|
|
138
137
|
# Regeneration support methods
|
|
139
138
|
async def truncate_conversation_after(
|
|
140
|
-
self,
|
|
141
|
-
|
|
142
|
-
message_id: MessageId
|
|
143
|
-
) -> Result[int, Union['MemoryNotFoundError', 'MemoryStorageError']]:
|
|
139
|
+
self, conversation_id: str, message_id: MessageId
|
|
140
|
+
) -> Result[int, Union["MemoryNotFoundError", "MemoryStorageError"]]:
|
|
144
141
|
"""
|
|
145
142
|
Truncate conversation after (and including) the specified message ID.
|
|
146
143
|
Returns the number of messages removed.
|
|
@@ -148,10 +145,8 @@ class MemoryProvider(Protocol):
|
|
|
148
145
|
...
|
|
149
146
|
|
|
150
147
|
async def get_conversation_until_message(
|
|
151
|
-
self,
|
|
152
|
-
|
|
153
|
-
message_id: MessageId
|
|
154
|
-
) -> Result[Optional[ConversationMemory], Union['MemoryNotFoundError', 'MemoryStorageError']]:
|
|
148
|
+
self, conversation_id: str, message_id: MessageId
|
|
149
|
+
) -> Result[Optional[ConversationMemory], Union["MemoryNotFoundError", "MemoryStorageError"]]:
|
|
155
150
|
"""
|
|
156
151
|
Get conversation history up to (but not including) the specified message ID.
|
|
157
152
|
Useful for regeneration scenarios.
|
|
@@ -159,26 +154,28 @@ class MemoryProvider(Protocol):
|
|
|
159
154
|
...
|
|
160
155
|
|
|
161
156
|
async def mark_regeneration_point(
|
|
162
|
-
self,
|
|
163
|
-
|
|
164
|
-
message_id: MessageId,
|
|
165
|
-
regeneration_metadata: Dict[str, Any]
|
|
166
|
-
) -> Result[None, Union['MemoryNotFoundError', 'MemoryStorageError']]:
|
|
157
|
+
self, conversation_id: str, message_id: MessageId, regeneration_metadata: Dict[str, Any]
|
|
158
|
+
) -> Result[None, Union["MemoryNotFoundError", "MemoryStorageError"]]:
|
|
167
159
|
"""
|
|
168
160
|
Mark a regeneration point in the conversation for audit purposes.
|
|
169
161
|
"""
|
|
170
162
|
...
|
|
171
163
|
|
|
164
|
+
|
|
172
165
|
# Configuration models using Pydantic for validation
|
|
173
166
|
|
|
167
|
+
|
|
174
168
|
class InMemoryConfig(BaseModel):
|
|
175
169
|
"""Configuration for in-memory provider."""
|
|
170
|
+
|
|
176
171
|
type: str = Field(default="memory", json_schema_extra={"literal": True})
|
|
177
172
|
max_conversations: int = Field(default=1000, ge=1)
|
|
178
173
|
max_messages_per_conversation: int = Field(default=1000, ge=1)
|
|
179
174
|
|
|
175
|
+
|
|
180
176
|
class RedisConfig(BaseModel):
|
|
181
177
|
"""Configuration for Redis provider."""
|
|
178
|
+
|
|
182
179
|
type: str = Field(default="redis", json_schema_extra={"literal": True})
|
|
183
180
|
url: Optional[str] = None
|
|
184
181
|
host: str = Field(default="localhost")
|
|
@@ -188,8 +185,10 @@ class RedisConfig(BaseModel):
|
|
|
188
185
|
key_prefix: str = Field(default="jaf:memory:")
|
|
189
186
|
ttl: Optional[int] = Field(default=None, ge=1) # seconds
|
|
190
187
|
|
|
188
|
+
|
|
191
189
|
class PostgresConfig(BaseModel):
|
|
192
190
|
"""Configuration for PostgreSQL provider."""
|
|
191
|
+
|
|
193
192
|
type: str = Field(default="postgres", json_schema_extra={"literal": True})
|
|
194
193
|
connection_string: Optional[str] = None
|
|
195
194
|
host: str = Field(default="localhost")
|
|
@@ -201,13 +200,16 @@ class PostgresConfig(BaseModel):
|
|
|
201
200
|
table_name: str = Field(default="conversations")
|
|
202
201
|
max_connections: int = Field(default=10, ge=1)
|
|
203
202
|
|
|
203
|
+
|
|
204
204
|
# Union type for all provider configurations
|
|
205
205
|
MemoryProviderConfig = Union[InMemoryConfig, RedisConfig, PostgresConfig]
|
|
206
206
|
|
|
207
207
|
# Functional error types for memory providers
|
|
208
208
|
|
|
209
|
+
|
|
209
210
|
class MemoryError:
|
|
210
211
|
"""Base class for memory-related errors."""
|
|
212
|
+
|
|
211
213
|
def __init__(self, message: str, provider: str, cause: Optional[Exception] = None):
|
|
212
214
|
self.message = message
|
|
213
215
|
self.provider = provider
|
|
@@ -216,20 +218,28 @@ class MemoryError:
|
|
|
216
218
|
def __eq__(self, other):
|
|
217
219
|
if not isinstance(other, MemoryError):
|
|
218
220
|
return False
|
|
219
|
-
return (
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
return (
|
|
222
|
+
self.message == other.message
|
|
223
|
+
and self.provider == other.provider
|
|
224
|
+
and self.cause == other.cause
|
|
225
|
+
)
|
|
222
226
|
|
|
223
227
|
def __repr__(self):
|
|
224
228
|
return f"{self.__class__.__name__}(message={self.message!r}, provider={self.provider!r}, cause={self.cause!r})"
|
|
225
229
|
|
|
230
|
+
|
|
226
231
|
class MemoryConnectionError(MemoryError):
|
|
227
232
|
"""Error for connection failures."""
|
|
233
|
+
|
|
228
234
|
pass
|
|
229
235
|
|
|
236
|
+
|
|
230
237
|
class MemoryNotFoundError(MemoryError):
|
|
231
238
|
"""Error when a conversation is not found."""
|
|
232
|
-
|
|
239
|
+
|
|
240
|
+
def __init__(
|
|
241
|
+
self, message: str, provider: str, conversation_id: str, cause: Optional[Exception] = None
|
|
242
|
+
):
|
|
233
243
|
super().__init__(message, provider, cause)
|
|
234
244
|
self.conversation_id = conversation_id
|
|
235
245
|
|
|
@@ -241,9 +251,13 @@ class MemoryNotFoundError(MemoryError):
|
|
|
241
251
|
def __repr__(self):
|
|
242
252
|
return f"{self.__class__.__name__}(message={self.message!r}, provider={self.provider!r}, conversation_id={self.conversation_id!r}, cause={self.cause!r})"
|
|
243
253
|
|
|
254
|
+
|
|
244
255
|
class MemoryStorageError(MemoryError):
|
|
245
256
|
"""Error for storage operation failures."""
|
|
246
|
-
|
|
257
|
+
|
|
258
|
+
def __init__(
|
|
259
|
+
self, message: str, provider: str, operation: str, cause: Optional[Exception] = None
|
|
260
|
+
):
|
|
247
261
|
super().__init__(message, provider, cause)
|
|
248
262
|
self.operation = operation
|
|
249
263
|
|
|
@@ -255,16 +269,21 @@ class MemoryStorageError(MemoryError):
|
|
|
255
269
|
def __repr__(self):
|
|
256
270
|
return f"{self.__class__.__name__}(message={self.message!r}, provider={self.provider!r}, operation={self.operation!r}, cause={self.cause!r})"
|
|
257
271
|
|
|
272
|
+
|
|
258
273
|
# Union of all possible memory errors
|
|
259
274
|
MemoryErrorUnion = Union[MemoryConnectionError, MemoryNotFoundError, MemoryStorageError]
|
|
260
275
|
|
|
276
|
+
|
|
261
277
|
# Memory configuration for the engine - using dataclass instead of Pydantic
|
|
262
278
|
@dataclass(frozen=True)
|
|
263
279
|
class MemoryConfig:
|
|
264
280
|
"""Configuration for memory integration in the engine."""
|
|
281
|
+
|
|
265
282
|
provider: MemoryProvider
|
|
266
283
|
auto_store: bool = True
|
|
267
284
|
max_messages: Optional[int] = None
|
|
268
285
|
ttl: Optional[int] = None
|
|
269
286
|
compression_threshold: Optional[int] = None
|
|
270
|
-
store_on_completion: bool =
|
|
287
|
+
store_on_completion: bool = (
|
|
288
|
+
True # Store conversation on completion (in addition to interruptions)
|
|
289
|
+
)
|
jaf/memory/utils.py
CHANGED
|
@@ -16,7 +16,7 @@ from .types import ConversationMemory
|
|
|
16
16
|
def serialize_message(msg: Message) -> dict:
|
|
17
17
|
"""
|
|
18
18
|
Convert Message dataclass to dict for storage.
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
This provides a consistent serialization format across all memory providers.
|
|
21
21
|
"""
|
|
22
22
|
return {
|
|
@@ -28,19 +28,19 @@ def serialize_message(msg: Message) -> dict:
|
|
|
28
28
|
{
|
|
29
29
|
"id": tc.id,
|
|
30
30
|
"type": tc.type,
|
|
31
|
-
"function": {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
"function": {"name": tc.function.name, "arguments": tc.function.arguments},
|
|
32
|
+
}
|
|
33
|
+
for tc in msg.tool_calls
|
|
34
|
+
]
|
|
35
|
+
if msg.tool_calls
|
|
36
|
+
else None,
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def deserialize_message(msg_data: dict) -> Message:
|
|
41
41
|
"""
|
|
42
42
|
Convert dict back to Message dataclass from storage.
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
This provides a consistent deserialization format across all memory providers.
|
|
45
45
|
"""
|
|
46
46
|
tool_calls = None
|
|
@@ -50,10 +50,10 @@ def deserialize_message(msg_data: dict) -> Message:
|
|
|
50
50
|
id=tc["id"],
|
|
51
51
|
type=tc["type"],
|
|
52
52
|
function=ToolCallFunction(
|
|
53
|
-
name=tc["function"]["name"],
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
name=tc["function"]["name"], arguments=tc["function"]["arguments"]
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
for tc in msg_data["tool_calls"]
|
|
57
57
|
]
|
|
58
58
|
|
|
59
59
|
return Message(
|
|
@@ -61,17 +61,17 @@ def deserialize_message(msg_data: dict) -> Message:
|
|
|
61
61
|
content=msg_data["content"],
|
|
62
62
|
message_id=msg_data.get("message_id"),
|
|
63
63
|
tool_call_id=msg_data.get("tool_call_id"),
|
|
64
|
-
tool_calls=tool_calls
|
|
64
|
+
tool_calls=tool_calls,
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def serialize_conversation_for_json(conversation: ConversationMemory) -> str:
|
|
69
69
|
"""
|
|
70
70
|
Serialize conversation to JSON string for storage (Redis, file systems, etc.).
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
Args:
|
|
73
73
|
conversation: The conversation memory to serialize
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
Returns:
|
|
76
76
|
JSON string representation
|
|
77
77
|
"""
|
|
@@ -82,7 +82,7 @@ def serialize_conversation_for_json(conversation: ConversationMemory) -> str:
|
|
|
82
82
|
"metadata": {
|
|
83
83
|
k: v.isoformat() if isinstance(v, datetime) else v
|
|
84
84
|
for k, v in conversation.metadata.items()
|
|
85
|
-
}
|
|
85
|
+
},
|
|
86
86
|
}
|
|
87
87
|
return json.dumps(data)
|
|
88
88
|
|
|
@@ -90,10 +90,10 @@ def serialize_conversation_for_json(conversation: ConversationMemory) -> str:
|
|
|
90
90
|
def deserialize_conversation_from_json(data: str) -> ConversationMemory:
|
|
91
91
|
"""
|
|
92
92
|
Deserialize conversation from JSON string.
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
Args:
|
|
95
95
|
data: JSON string representation
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
Returns:
|
|
98
98
|
ConversationMemory instance
|
|
99
99
|
"""
|
|
@@ -114,17 +114,17 @@ def deserialize_conversation_from_json(data: str) -> ConversationMemory:
|
|
|
114
114
|
conversation_id=parsed["conversation_id"],
|
|
115
115
|
user_id=parsed.get("user_id"),
|
|
116
116
|
messages=[deserialize_message(msg) for msg in parsed.get("messages", [])],
|
|
117
|
-
metadata=metadata
|
|
117
|
+
metadata=metadata,
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def prepare_message_list_for_db(messages: List[Message]) -> str:
|
|
122
122
|
"""
|
|
123
123
|
Prepare a list of messages for database storage.
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
Args:
|
|
126
126
|
messages: List of Message objects
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
Returns:
|
|
129
129
|
JSON string suitable for database storage
|
|
130
130
|
"""
|
|
@@ -134,10 +134,10 @@ def prepare_message_list_for_db(messages: List[Message]) -> str:
|
|
|
134
134
|
def extract_messages_from_db_row(messages_json: str) -> List[Message]:
|
|
135
135
|
"""
|
|
136
136
|
Extract messages from database row JSON.
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
Args:
|
|
139
139
|
messages_json: JSON string from database
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
Returns:
|
|
142
142
|
List of Message objects
|
|
143
143
|
"""
|
|
@@ -148,10 +148,10 @@ def extract_messages_from_db_row(messages_json: str) -> List[Message]:
|
|
|
148
148
|
def sanitize_conversation_id(conversation_id: str) -> str:
|
|
149
149
|
"""
|
|
150
150
|
Sanitize conversation ID to ensure it's safe for storage.
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
Args:
|
|
153
153
|
conversation_id: Raw conversation ID
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
Returns:
|
|
156
156
|
Sanitized conversation ID
|
|
157
157
|
"""
|
|
@@ -160,14 +160,16 @@ def sanitize_conversation_id(conversation_id: str) -> str:
|
|
|
160
160
|
return sanitized[:100] # Limit length for database compatibility
|
|
161
161
|
|
|
162
162
|
|
|
163
|
-
def create_default_metadata(
|
|
163
|
+
def create_default_metadata(
|
|
164
|
+
user_id: Optional[str] = None, message_count: int = 0
|
|
165
|
+
) -> Dict[str, Any]:
|
|
164
166
|
"""
|
|
165
167
|
Create default metadata for a new conversation.
|
|
166
|
-
|
|
168
|
+
|
|
167
169
|
Args:
|
|
168
170
|
user_id: Optional user ID
|
|
169
171
|
message_count: Initial message count
|
|
170
|
-
|
|
172
|
+
|
|
171
173
|
Returns:
|
|
172
174
|
Default metadata dictionary
|
|
173
175
|
"""
|
|
@@ -177,23 +179,23 @@ def create_default_metadata(user_id: Optional[str] = None, message_count: int =
|
|
|
177
179
|
"updated_at": now,
|
|
178
180
|
"last_activity": now,
|
|
179
181
|
"total_messages": message_count,
|
|
180
|
-
"user_id": user_id
|
|
182
|
+
"user_id": user_id,
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
|
|
184
186
|
def update_conversation_metadata(
|
|
185
187
|
existing_metadata: Dict[str, Any],
|
|
186
188
|
new_message_count: int,
|
|
187
|
-
additional_metadata: Optional[Dict[str, Any]] = None
|
|
189
|
+
additional_metadata: Optional[Dict[str, Any]] = None,
|
|
188
190
|
) -> Dict[str, Any]:
|
|
189
191
|
"""
|
|
190
192
|
Update conversation metadata with new activity.
|
|
191
|
-
|
|
193
|
+
|
|
192
194
|
Args:
|
|
193
195
|
existing_metadata: Current metadata
|
|
194
196
|
new_message_count: Updated message count
|
|
195
197
|
additional_metadata: Additional metadata to merge
|
|
196
|
-
|
|
198
|
+
|
|
197
199
|
Returns:
|
|
198
200
|
Updated metadata dictionary
|
|
199
201
|
"""
|
|
@@ -215,10 +217,10 @@ def update_conversation_metadata(
|
|
|
215
217
|
def validate_conversation_metadata(metadata: Dict[str, Any]) -> Dict[str, Any]:
|
|
216
218
|
"""
|
|
217
219
|
Validate and clean conversation metadata.
|
|
218
|
-
|
|
220
|
+
|
|
219
221
|
Args:
|
|
220
222
|
metadata: Raw metadata dictionary
|
|
221
|
-
|
|
223
|
+
|
|
222
224
|
Returns:
|
|
223
225
|
Validated and cleaned metadata
|
|
224
226
|
"""
|
jaf/plugins/__init__.py
CHANGED
|
@@ -13,16 +13,16 @@ from .manager import PluginManager
|
|
|
13
13
|
from .decorators import plugin, tool_provider, model_provider, memory_provider
|
|
14
14
|
|
|
15
15
|
__all__ = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
"JAFPlugin",
|
|
17
|
+
"PluginMetadata",
|
|
18
|
+
"PluginStatus",
|
|
19
|
+
"PluginRegistry",
|
|
20
|
+
"PluginLoader",
|
|
21
|
+
"PluginManager",
|
|
22
|
+
"get_plugin_registry",
|
|
23
|
+
"load_plugins_from_directory",
|
|
24
|
+
"plugin",
|
|
25
|
+
"tool_provider",
|
|
26
|
+
"model_provider",
|
|
27
|
+
"memory_provider",
|
|
28
28
|
]
|