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/a2a/memory/serialization.py
CHANGED
|
@@ -18,6 +18,7 @@ from .types import A2AResult, create_a2a_failure, create_a2a_success, create_a2a
|
|
|
18
18
|
@dataclass(frozen=True)
|
|
19
19
|
class A2ATaskSerialized:
|
|
20
20
|
"""Serialized representation of an A2A task for storage"""
|
|
21
|
+
|
|
21
22
|
task_id: str
|
|
22
23
|
context_id: str
|
|
23
24
|
state: str
|
|
@@ -27,17 +28,17 @@ class A2ATaskSerialized:
|
|
|
27
28
|
status_message: Optional[str] = None # Serialized status message for quick access
|
|
28
29
|
metadata: Optional[str] = None # JSON string of metadata
|
|
29
30
|
|
|
31
|
+
|
|
30
32
|
def serialize_a2a_task(
|
|
31
|
-
task: A2ATask,
|
|
32
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
33
|
+
task: A2ATask, metadata: Optional[Dict[str, Any]] = None
|
|
33
34
|
) -> A2AResult[A2ATaskSerialized]:
|
|
34
35
|
"""
|
|
35
36
|
Pure function to serialize an A2A task for storage
|
|
36
|
-
|
|
37
|
+
|
|
37
38
|
Args:
|
|
38
39
|
task: The A2A task to serialize
|
|
39
40
|
metadata: Optional metadata to store with the task
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
Returns:
|
|
42
43
|
A2AResult containing the serialized task or an error
|
|
43
44
|
"""
|
|
@@ -67,7 +68,7 @@ def serialize_a2a_task(
|
|
|
67
68
|
for key, value in task.metadata.items():
|
|
68
69
|
if isinstance(value, A2ATask):
|
|
69
70
|
raise TypeError(f"Object of type A2ATask is not JSON serializable")
|
|
70
|
-
|
|
71
|
+
|
|
71
72
|
task_data = task.model_dump_json(by_alias=True)
|
|
72
73
|
except TypeError as e:
|
|
73
74
|
# Re-raise TypeError (including circular reference errors) directly
|
|
@@ -83,9 +84,10 @@ def serialize_a2a_task(
|
|
|
83
84
|
if isinstance(obj, datetime):
|
|
84
85
|
return obj.isoformat()
|
|
85
86
|
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
|
|
87
|
+
|
|
86
88
|
metadata_str = None
|
|
87
89
|
if metadata:
|
|
88
|
-
metadata_str = json.dumps(metadata, separators=(
|
|
90
|
+
metadata_str = json.dumps(metadata, separators=(",", ":"), default=datetime_converter)
|
|
89
91
|
|
|
90
92
|
serialized = A2ATaskSerialized(
|
|
91
93
|
task_id=task.id,
|
|
@@ -102,16 +104,17 @@ def serialize_a2a_task(
|
|
|
102
104
|
|
|
103
105
|
except Exception as error:
|
|
104
106
|
return create_a2a_failure(
|
|
105
|
-
create_a2a_task_storage_error(
|
|
107
|
+
create_a2a_task_storage_error("serialize", "memory", task.id, error)
|
|
106
108
|
)
|
|
107
109
|
|
|
110
|
+
|
|
108
111
|
def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
|
|
109
112
|
"""
|
|
110
113
|
Pure function to deserialize an A2A task from storage
|
|
111
|
-
|
|
114
|
+
|
|
112
115
|
Args:
|
|
113
116
|
stored: The serialized task data
|
|
114
|
-
|
|
117
|
+
|
|
115
118
|
Returns:
|
|
116
119
|
A2AResult containing the deserialized task or an error
|
|
117
120
|
"""
|
|
@@ -126,10 +129,7 @@ def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
|
|
|
126
129
|
# Convert Pydantic validation errors to our format
|
|
127
130
|
return create_a2a_failure(
|
|
128
131
|
create_a2a_task_storage_error(
|
|
129
|
-
|
|
130
|
-
'memory',
|
|
131
|
-
stored.task_id,
|
|
132
|
-
Exception('Invalid task structure')
|
|
132
|
+
"deserialize", "memory", stored.task_id, Exception("Invalid task structure")
|
|
133
133
|
)
|
|
134
134
|
)
|
|
135
135
|
|
|
@@ -137,10 +137,7 @@ def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
|
|
|
137
137
|
if not task.id or not task.context_id or not task.status or task.kind != "task":
|
|
138
138
|
return create_a2a_failure(
|
|
139
139
|
create_a2a_task_storage_error(
|
|
140
|
-
|
|
141
|
-
'memory',
|
|
142
|
-
stored.task_id,
|
|
143
|
-
Exception('Invalid task structure')
|
|
140
|
+
"deserialize", "memory", stored.task_id, Exception("Invalid task structure")
|
|
144
141
|
)
|
|
145
142
|
)
|
|
146
143
|
|
|
@@ -148,43 +145,43 @@ def deserialize_a2a_task(stored: A2ATaskSerialized) -> A2AResult[A2ATask]:
|
|
|
148
145
|
|
|
149
146
|
except Exception as error:
|
|
150
147
|
return create_a2a_failure(
|
|
151
|
-
create_a2a_task_storage_error(
|
|
148
|
+
create_a2a_task_storage_error("deserialize", "memory", stored.task_id, error)
|
|
152
149
|
)
|
|
153
150
|
|
|
151
|
+
|
|
154
152
|
def create_task_index(task: A2ATask) -> A2AResult[Dict[str, Any]]:
|
|
155
153
|
"""
|
|
156
154
|
Pure function to create a minimal task representation for indexing
|
|
157
|
-
|
|
155
|
+
|
|
158
156
|
Args:
|
|
159
157
|
task: The A2A task to index
|
|
160
|
-
|
|
158
|
+
|
|
161
159
|
Returns:
|
|
162
160
|
A2AResult containing the task index data or an error
|
|
163
161
|
"""
|
|
164
162
|
try:
|
|
165
163
|
index_data = {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
164
|
+
"task_id": task.id,
|
|
165
|
+
"context_id": task.context_id,
|
|
166
|
+
"state": task.status.state.value,
|
|
167
|
+
"timestamp": task.status.timestamp or datetime.now().isoformat(),
|
|
168
|
+
"has_history": bool(task.history and len(task.history) > 0),
|
|
169
|
+
"has_artifacts": bool(task.artifacts and len(task.artifacts) > 0),
|
|
172
170
|
}
|
|
173
171
|
|
|
174
172
|
return create_a2a_success(index_data)
|
|
175
173
|
|
|
176
174
|
except Exception as error:
|
|
177
|
-
return create_a2a_failure(
|
|
178
|
-
|
|
179
|
-
)
|
|
175
|
+
return create_a2a_failure(create_a2a_task_storage_error("index", "memory", task.id, error))
|
|
176
|
+
|
|
180
177
|
|
|
181
178
|
def extract_task_search_text(task: A2ATask) -> A2AResult[str]:
|
|
182
179
|
"""
|
|
183
180
|
Pure function to extract searchable text from a task for full-text search
|
|
184
|
-
|
|
181
|
+
|
|
185
182
|
Args:
|
|
186
183
|
task: The A2A task to extract text from
|
|
187
|
-
|
|
184
|
+
|
|
188
185
|
Returns:
|
|
189
186
|
A2AResult containing the extracted text or an error
|
|
190
187
|
"""
|
|
@@ -212,13 +209,14 @@ def extract_task_search_text(task: A2ATask) -> A2AResult[str]:
|
|
|
212
209
|
if part.kind == "text":
|
|
213
210
|
text_parts.append(part.text)
|
|
214
211
|
|
|
215
|
-
return create_a2a_success(
|
|
212
|
+
return create_a2a_success(" ".join(text_parts).strip())
|
|
216
213
|
|
|
217
214
|
except Exception as error:
|
|
218
215
|
return create_a2a_failure(
|
|
219
|
-
create_a2a_task_storage_error(
|
|
216
|
+
create_a2a_task_storage_error("extract-text", "memory", task.id, error)
|
|
220
217
|
)
|
|
221
218
|
|
|
219
|
+
|
|
222
220
|
def _extract_text_from_message(message: A2AMessage, text_parts: List[str]) -> None:
|
|
223
221
|
"""Helper function to extract text from A2A message parts"""
|
|
224
222
|
for part in message.parts:
|
|
@@ -232,13 +230,14 @@ def _extract_text_from_message(message: A2AMessage, text_parts: List[str]) -> No
|
|
|
232
230
|
elif part.kind == "file" and part.file.name:
|
|
233
231
|
text_parts.append(part.file.name)
|
|
234
232
|
|
|
233
|
+
|
|
235
234
|
def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
|
|
236
235
|
"""
|
|
237
236
|
Pure function to validate task data integrity
|
|
238
|
-
|
|
237
|
+
|
|
239
238
|
Args:
|
|
240
239
|
task: The A2A task to validate
|
|
241
|
-
|
|
240
|
+
|
|
242
241
|
Returns:
|
|
243
242
|
A2AResult containing True if valid or an error
|
|
244
243
|
"""
|
|
@@ -247,40 +246,31 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
|
|
|
247
246
|
if not task.id or not isinstance(task.id, str):
|
|
248
247
|
return create_a2a_failure(
|
|
249
248
|
create_a2a_task_storage_error(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
getattr(task,
|
|
253
|
-
Exception(
|
|
249
|
+
"validate",
|
|
250
|
+
"memory",
|
|
251
|
+
getattr(task, "id", None),
|
|
252
|
+
Exception("Task ID is required and must be a string"),
|
|
254
253
|
)
|
|
255
254
|
)
|
|
256
255
|
|
|
257
256
|
if not task.context_id or not isinstance(task.context_id, str):
|
|
258
257
|
return create_a2a_failure(
|
|
259
258
|
create_a2a_task_storage_error(
|
|
260
|
-
|
|
261
|
-
'memory',
|
|
262
|
-
task.id,
|
|
263
|
-
Exception('Context ID is required')
|
|
259
|
+
"validate", "memory", task.id, Exception("Context ID is required")
|
|
264
260
|
)
|
|
265
261
|
)
|
|
266
262
|
|
|
267
263
|
if not task.status or not task.status.state:
|
|
268
264
|
return create_a2a_failure(
|
|
269
265
|
create_a2a_task_storage_error(
|
|
270
|
-
|
|
271
|
-
'memory',
|
|
272
|
-
task.id,
|
|
273
|
-
Exception('Task status and state are required')
|
|
266
|
+
"validate", "memory", task.id, Exception("Task status and state are required")
|
|
274
267
|
)
|
|
275
268
|
)
|
|
276
269
|
|
|
277
270
|
if task.kind != "task":
|
|
278
271
|
return create_a2a_failure(
|
|
279
272
|
create_a2a_task_storage_error(
|
|
280
|
-
|
|
281
|
-
'memory',
|
|
282
|
-
task.id,
|
|
283
|
-
Exception('Task kind must be "task"')
|
|
273
|
+
"validate", "memory", task.id, Exception('Task kind must be "task"')
|
|
284
274
|
)
|
|
285
275
|
)
|
|
286
276
|
|
|
@@ -289,21 +279,22 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
|
|
|
289
279
|
if not isinstance(task.history, list):
|
|
290
280
|
return create_a2a_failure(
|
|
291
281
|
create_a2a_task_storage_error(
|
|
292
|
-
|
|
293
|
-
'memory',
|
|
294
|
-
task.id,
|
|
295
|
-
Exception('Task history must be a list')
|
|
282
|
+
"validate", "memory", task.id, Exception("Task history must be a list")
|
|
296
283
|
)
|
|
297
284
|
)
|
|
298
285
|
|
|
299
286
|
for i, message in enumerate(task.history):
|
|
300
|
-
if
|
|
287
|
+
if (
|
|
288
|
+
not message.message_id
|
|
289
|
+
or not message.parts
|
|
290
|
+
or not isinstance(message.parts, list)
|
|
291
|
+
):
|
|
301
292
|
return create_a2a_failure(
|
|
302
293
|
create_a2a_task_storage_error(
|
|
303
|
-
|
|
304
|
-
|
|
294
|
+
"validate",
|
|
295
|
+
"memory",
|
|
305
296
|
task.id,
|
|
306
|
-
Exception(f
|
|
297
|
+
Exception(f"Invalid message at index {i} in task history"),
|
|
307
298
|
)
|
|
308
299
|
)
|
|
309
300
|
|
|
@@ -312,21 +303,22 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
|
|
|
312
303
|
if not isinstance(task.artifacts, list):
|
|
313
304
|
return create_a2a_failure(
|
|
314
305
|
create_a2a_task_storage_error(
|
|
315
|
-
|
|
316
|
-
'memory',
|
|
317
|
-
task.id,
|
|
318
|
-
Exception('Task artifacts must be a list')
|
|
306
|
+
"validate", "memory", task.id, Exception("Task artifacts must be a list")
|
|
319
307
|
)
|
|
320
308
|
)
|
|
321
309
|
|
|
322
310
|
for i, artifact in enumerate(task.artifacts):
|
|
323
|
-
if
|
|
311
|
+
if (
|
|
312
|
+
not artifact.artifact_id
|
|
313
|
+
or not artifact.parts
|
|
314
|
+
or not isinstance(artifact.parts, list)
|
|
315
|
+
):
|
|
324
316
|
return create_a2a_failure(
|
|
325
317
|
create_a2a_task_storage_error(
|
|
326
|
-
|
|
327
|
-
|
|
318
|
+
"validate",
|
|
319
|
+
"memory",
|
|
328
320
|
task.id,
|
|
329
|
-
Exception(f
|
|
321
|
+
Exception(f"Invalid artifact at index {i} in task"),
|
|
330
322
|
)
|
|
331
323
|
)
|
|
332
324
|
|
|
@@ -334,16 +326,17 @@ def validate_task_integrity(task: A2ATask) -> A2AResult[bool]:
|
|
|
334
326
|
|
|
335
327
|
except Exception as error:
|
|
336
328
|
return create_a2a_failure(
|
|
337
|
-
create_a2a_task_storage_error(
|
|
329
|
+
create_a2a_task_storage_error("validate", "memory", getattr(task, "id", None), error)
|
|
338
330
|
)
|
|
339
331
|
|
|
332
|
+
|
|
340
333
|
def clone_task(task: A2ATask) -> A2AResult[A2ATask]:
|
|
341
334
|
"""
|
|
342
335
|
Pure function to create a deep copy of a task (for immutability)
|
|
343
|
-
|
|
336
|
+
|
|
344
337
|
Args:
|
|
345
338
|
task: The A2A task to clone
|
|
346
|
-
|
|
339
|
+
|
|
347
340
|
Returns:
|
|
348
341
|
A2AResult containing the cloned task or an error
|
|
349
342
|
"""
|
|
@@ -353,18 +346,17 @@ def clone_task(task: A2ATask) -> A2AResult[A2ATask]:
|
|
|
353
346
|
return create_a2a_success(cloned)
|
|
354
347
|
|
|
355
348
|
except Exception as error:
|
|
356
|
-
return create_a2a_failure(
|
|
357
|
-
|
|
358
|
-
)
|
|
349
|
+
return create_a2a_failure(create_a2a_task_storage_error("clone", "memory", task.id, error))
|
|
350
|
+
|
|
359
351
|
|
|
360
352
|
def sanitize_task(task: A2ATask) -> A2AResult[A2ATask]:
|
|
361
353
|
"""
|
|
362
354
|
Pure function to sanitize task data for storage
|
|
363
355
|
Removes any potentially dangerous or invalid data
|
|
364
|
-
|
|
356
|
+
|
|
365
357
|
Args:
|
|
366
358
|
task: The A2A task to sanitize
|
|
367
|
-
|
|
359
|
+
|
|
368
360
|
Returns:
|
|
369
361
|
A2AResult containing the sanitized task or an error
|
|
370
362
|
"""
|
|
@@ -385,24 +377,22 @@ def sanitize_task(task: A2ATask) -> A2AResult[A2ATask]:
|
|
|
385
377
|
if sanitized.status.timestamp:
|
|
386
378
|
try:
|
|
387
379
|
# Parse and re-format timestamp to ensure it's valid
|
|
388
|
-
dt = datetime.fromisoformat(sanitized.status.timestamp.replace(
|
|
380
|
+
dt = datetime.fromisoformat(sanitized.status.timestamp.replace("Z", "+00:00"))
|
|
389
381
|
# Create a new task with the corrected timestamp
|
|
390
|
-
sanitized = sanitized.model_copy(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
382
|
+
sanitized = sanitized.model_copy(
|
|
383
|
+
update={
|
|
384
|
+
"status": sanitized.status.model_copy(update={"timestamp": dt.isoformat()})
|
|
385
|
+
}
|
|
386
|
+
)
|
|
395
387
|
except Exception:
|
|
396
388
|
# Remove invalid timestamp
|
|
397
|
-
sanitized = sanitized.model_copy(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
})
|
|
401
|
-
})
|
|
389
|
+
sanitized = sanitized.model_copy(
|
|
390
|
+
update={"status": sanitized.status.model_copy(update={"timestamp": None})}
|
|
391
|
+
)
|
|
402
392
|
|
|
403
393
|
return create_a2a_success(sanitized)
|
|
404
394
|
|
|
405
395
|
except Exception as error:
|
|
406
396
|
return create_a2a_failure(
|
|
407
|
-
create_a2a_task_storage_error(
|
|
397
|
+
create_a2a_task_storage_error("sanitize", "memory", getattr(task, "id", None), error)
|
|
408
398
|
)
|