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
|
@@ -40,7 +40,7 @@ class TestA2ATaskSerialization:
|
|
|
40
40
|
self,
|
|
41
41
|
task_id: str = "task_123",
|
|
42
42
|
context_id: str = "ctx_456",
|
|
43
|
-
state: TaskState = TaskState.WORKING
|
|
43
|
+
state: TaskState = TaskState.WORKING,
|
|
44
44
|
) -> A2ATask:
|
|
45
45
|
"""Helper to create a comprehensive test task"""
|
|
46
46
|
return A2ATask(
|
|
@@ -54,9 +54,9 @@ class TestA2ATaskSerialization:
|
|
|
54
54
|
parts=[A2ATextPart(kind="text", text=f"Processing task {task_id}...")],
|
|
55
55
|
messageId=f"msg_{task_id}",
|
|
56
56
|
contextId=context_id,
|
|
57
|
-
kind="message"
|
|
57
|
+
kind="message",
|
|
58
58
|
),
|
|
59
|
-
timestamp=datetime.now(timezone.utc).isoformat()
|
|
59
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
60
60
|
),
|
|
61
61
|
history=[
|
|
62
62
|
A2AMessage(
|
|
@@ -64,15 +64,15 @@ class TestA2ATaskSerialization:
|
|
|
64
64
|
parts=[A2ATextPart(kind="text", text="Hello, please help me")],
|
|
65
65
|
messageId="msg_001",
|
|
66
66
|
contextId=context_id,
|
|
67
|
-
kind="message"
|
|
67
|
+
kind="message",
|
|
68
68
|
),
|
|
69
69
|
A2AMessage(
|
|
70
70
|
role="agent",
|
|
71
71
|
parts=[A2ADataPart(kind="data", data={"progress": 50, "status": "processing"})],
|
|
72
72
|
messageId="msg_002",
|
|
73
73
|
contextId=context_id,
|
|
74
|
-
kind="message"
|
|
75
|
-
)
|
|
74
|
+
kind="message",
|
|
75
|
+
),
|
|
76
76
|
],
|
|
77
77
|
artifacts=[
|
|
78
78
|
A2AArtifact(
|
|
@@ -81,15 +81,17 @@ class TestA2ATaskSerialization:
|
|
|
81
81
|
description="A comprehensive test artifact",
|
|
82
82
|
parts=[
|
|
83
83
|
A2ATextPart(kind="text", text="Artifact content"),
|
|
84
|
-
A2AFilePart(
|
|
85
|
-
|
|
84
|
+
A2AFilePart(
|
|
85
|
+
kind="file", file=A2AFile(name="test.txt", mimeType="text/plain")
|
|
86
|
+
),
|
|
87
|
+
],
|
|
86
88
|
)
|
|
87
89
|
],
|
|
88
90
|
metadata={
|
|
89
91
|
"createdAt": datetime.now(timezone.utc).isoformat(),
|
|
90
92
|
"priority": "normal",
|
|
91
|
-
"source": "test"
|
|
92
|
-
}
|
|
93
|
+
"source": "test",
|
|
94
|
+
},
|
|
93
95
|
)
|
|
94
96
|
|
|
95
97
|
|
|
@@ -137,7 +139,7 @@ class TestSerializeA2ATask(TestA2ATaskSerialization):
|
|
|
137
139
|
id="task_minimal",
|
|
138
140
|
contextId="ctx_minimal",
|
|
139
141
|
kind="task",
|
|
140
|
-
status=A2ATaskStatus(state=TaskState.SUBMITTED)
|
|
142
|
+
status=A2ATaskStatus(state=TaskState.SUBMITTED),
|
|
141
143
|
)
|
|
142
144
|
|
|
143
145
|
result = serialize_a2a_task(minimal_task)
|
|
@@ -151,12 +153,14 @@ class TestSerializeA2ATask(TestA2ATaskSerialization):
|
|
|
151
153
|
"""Should gracefully handle circular references"""
|
|
152
154
|
task = self.create_test_task()
|
|
153
155
|
# Create circular reference by adding task to its own metadata
|
|
154
|
-
task.metadata[
|
|
156
|
+
task.metadata["circular"] = task
|
|
155
157
|
|
|
156
158
|
result = serialize_a2a_task(task)
|
|
157
|
-
|
|
159
|
+
|
|
158
160
|
assert result.error is not None, "Should fail with circular reference"
|
|
159
|
-
assert "JSON serializable" in str(result.error.cause),
|
|
161
|
+
assert "JSON serializable" in str(result.error.cause), (
|
|
162
|
+
"Should indicate JSON serialization error"
|
|
163
|
+
)
|
|
160
164
|
|
|
161
165
|
def test_serialize_datetime_objects(self):
|
|
162
166
|
"""Should properly convert datetime objects to ISO strings"""
|
|
@@ -169,7 +173,7 @@ class TestSerializeA2ATask(TestA2ATaskSerialization):
|
|
|
169
173
|
stored_metadata = json.loads(result.data.metadata)
|
|
170
174
|
assert isinstance(stored_metadata["timestamp"], str)
|
|
171
175
|
# Should be parseable as ISO datetime
|
|
172
|
-
datetime.fromisoformat(stored_metadata["timestamp"].replace(
|
|
176
|
+
datetime.fromisoformat(stored_metadata["timestamp"].replace("Z", "+00:00"))
|
|
173
177
|
|
|
174
178
|
|
|
175
179
|
class TestDeserializeA2ATask(TestA2ATaskSerialization):
|
|
@@ -200,7 +204,7 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
|
|
|
200
204
|
state="failed",
|
|
201
205
|
task_data="invalid json {{{", # Malformed JSON
|
|
202
206
|
created_at=datetime.now(timezone.utc).isoformat(),
|
|
203
|
-
updated_at=datetime.now(timezone.utc).isoformat()
|
|
207
|
+
updated_at=datetime.now(timezone.utc).isoformat(),
|
|
204
208
|
)
|
|
205
209
|
|
|
206
210
|
result = deserialize_a2a_task(invalid_serialized)
|
|
@@ -221,7 +225,7 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
|
|
|
221
225
|
state="failed",
|
|
222
226
|
task_data=json.dumps(incomplete_task_data),
|
|
223
227
|
created_at=datetime.now(timezone.utc).isoformat(),
|
|
224
|
-
updated_at=datetime.now(timezone.utc).isoformat()
|
|
228
|
+
updated_at=datetime.now(timezone.utc).isoformat(),
|
|
225
229
|
)
|
|
226
230
|
|
|
227
231
|
result = deserialize_a2a_task(incomplete_serialized)
|
|
@@ -241,9 +245,9 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
|
|
|
241
245
|
"role": "agent",
|
|
242
246
|
"parts": "not_a_list", # Should be a list
|
|
243
247
|
"messageId": "msg_corrupt",
|
|
244
|
-
"kind": "message"
|
|
245
|
-
}
|
|
246
|
-
}
|
|
248
|
+
"kind": "message",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
247
251
|
}
|
|
248
252
|
|
|
249
253
|
corrupt_serialized = A2ATaskSerialized(
|
|
@@ -252,7 +256,7 @@ class TestDeserializeA2ATask(TestA2ATaskSerialization):
|
|
|
252
256
|
state="working",
|
|
253
257
|
task_data=json.dumps(task_data),
|
|
254
258
|
created_at=datetime.now(timezone.utc).isoformat(),
|
|
255
|
-
updated_at=datetime.now(timezone.utc).isoformat()
|
|
259
|
+
updated_at=datetime.now(timezone.utc).isoformat(),
|
|
256
260
|
)
|
|
257
261
|
|
|
258
262
|
result = deserialize_a2a_task(corrupt_serialized)
|
|
@@ -313,17 +317,20 @@ class TestRoundTripSerialization(TestA2ATaskSerialization):
|
|
|
313
317
|
message=A2AMessage(
|
|
314
318
|
role="agent",
|
|
315
319
|
parts=[
|
|
316
|
-
A2ADataPart(
|
|
317
|
-
"
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
320
|
+
A2ADataPart(
|
|
321
|
+
kind="data",
|
|
322
|
+
data={
|
|
323
|
+
"nested": {"level": 2, "items": [1, 2, 3]},
|
|
324
|
+
"arrays": [{"id": 1}, {"id": 2}],
|
|
325
|
+
"unicode": "🔥 Special chars & symbols",
|
|
326
|
+
},
|
|
327
|
+
)
|
|
321
328
|
],
|
|
322
329
|
messageId="complex_msg",
|
|
323
330
|
contextId="complex_ctx",
|
|
324
|
-
kind="message"
|
|
325
|
-
)
|
|
326
|
-
)
|
|
331
|
+
kind="message",
|
|
332
|
+
),
|
|
333
|
+
),
|
|
327
334
|
)
|
|
328
335
|
|
|
329
336
|
# Round trip
|
|
@@ -338,7 +345,7 @@ class TestRoundTripSerialization(TestA2ATaskSerialization):
|
|
|
338
345
|
# Verify complex data structure preservation
|
|
339
346
|
if round_trip_task.status.message and round_trip_task.status.message.parts:
|
|
340
347
|
data_part = round_trip_task.status.message.parts[0]
|
|
341
|
-
if hasattr(data_part,
|
|
348
|
+
if hasattr(data_part, "data"):
|
|
342
349
|
assert data_part.data["nested"]["level"] == 2
|
|
343
350
|
assert data_part.data["arrays"][0]["id"] == 1
|
|
344
351
|
assert data_part.data["unicode"] == "🔥 Special chars & symbols"
|
|
@@ -368,7 +375,7 @@ class TestCreateTaskIndex(TestA2ATaskSerialization):
|
|
|
368
375
|
id="minimal",
|
|
369
376
|
contextId="ctx_minimal",
|
|
370
377
|
kind="task",
|
|
371
|
-
status=A2ATaskStatus(state=TaskState.SUBMITTED)
|
|
378
|
+
status=A2ATaskStatus(state=TaskState.SUBMITTED),
|
|
372
379
|
)
|
|
373
380
|
|
|
374
381
|
result = create_task_index(minimal_task)
|
|
@@ -412,19 +419,21 @@ class TestExtractTaskSearchText(TestA2ATaskSerialization):
|
|
|
412
419
|
state=TaskState.WORKING,
|
|
413
420
|
message=A2AMessage(
|
|
414
421
|
role="agent",
|
|
415
|
-
parts=[
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
422
|
+
parts=[
|
|
423
|
+
A2ADataPart(
|
|
424
|
+
kind="data",
|
|
425
|
+
data={
|
|
426
|
+
"title": "Important Document",
|
|
427
|
+
"summary": "This is a critical summary",
|
|
428
|
+
"count": 42, # Non-string data should be ignored
|
|
429
|
+
},
|
|
430
|
+
)
|
|
431
|
+
],
|
|
423
432
|
messageId="data_msg",
|
|
424
433
|
contextId="ctx_data",
|
|
425
|
-
kind="message"
|
|
426
|
-
)
|
|
427
|
-
)
|
|
434
|
+
kind="message",
|
|
435
|
+
),
|
|
436
|
+
),
|
|
428
437
|
)
|
|
429
438
|
|
|
430
439
|
result = extract_task_search_text(task_with_data)
|
|
@@ -440,7 +449,7 @@ class TestExtractTaskSearchText(TestA2ATaskSerialization):
|
|
|
440
449
|
id="empty",
|
|
441
450
|
contextId="ctx_empty",
|
|
442
451
|
kind="task",
|
|
443
|
-
status=A2ATaskStatus(state=TaskState.SUBMITTED)
|
|
452
|
+
status=A2ATaskStatus(state=TaskState.SUBMITTED),
|
|
444
453
|
)
|
|
445
454
|
|
|
446
455
|
result = extract_task_search_text(empty_task)
|
|
@@ -468,7 +477,9 @@ class TestValidateTaskIntegrity(TestA2ATaskSerialization):
|
|
|
468
477
|
result = validate_task_integrity(None) # Simulate missing ID scenario
|
|
469
478
|
|
|
470
479
|
assert result.error is not None
|
|
471
|
-
assert "Task ID is required" in str(result.error.message) or "validate" in str(
|
|
480
|
+
assert "Task ID is required" in str(result.error.message) or "validate" in str(
|
|
481
|
+
result.error.message
|
|
482
|
+
)
|
|
472
483
|
|
|
473
484
|
def test_reject_task_without_context_id(self):
|
|
474
485
|
"""Should reject task missing contextId"""
|
|
@@ -478,7 +489,7 @@ class TestValidateTaskIntegrity(TestA2ATaskSerialization):
|
|
|
478
489
|
id="test",
|
|
479
490
|
contextId="", # Invalid empty context ID
|
|
480
491
|
kind="task",
|
|
481
|
-
status=A2ATaskStatus(state=TaskState.SUBMITTED)
|
|
492
|
+
status=A2ATaskStatus(state=TaskState.SUBMITTED),
|
|
482
493
|
)
|
|
483
494
|
|
|
484
495
|
result = validate_task_integrity(invalid_task)
|
|
@@ -515,7 +526,7 @@ class TestValidateTaskIntegrity(TestA2ATaskSerialization):
|
|
|
515
526
|
id="test",
|
|
516
527
|
contextId="ctx",
|
|
517
528
|
kind="task", # This is actually correct, but we'll test the validation logic
|
|
518
|
-
status=A2ATaskStatus(state=TaskState.SUBMITTED)
|
|
529
|
+
status=A2ATaskStatus(state=TaskState.SUBMITTED),
|
|
519
530
|
)
|
|
520
531
|
|
|
521
532
|
# Manually set wrong kind for test
|
|
@@ -581,10 +592,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
|
|
|
581
592
|
id="timestamp_test",
|
|
582
593
|
contextId="ctx_test",
|
|
583
594
|
kind="task",
|
|
584
|
-
status=A2ATaskStatus(
|
|
585
|
-
state=TaskState.WORKING,
|
|
586
|
-
timestamp="invalid-date-string"
|
|
587
|
-
)
|
|
595
|
+
status=A2ATaskStatus(state=TaskState.WORKING, timestamp="invalid-date-string"),
|
|
588
596
|
)
|
|
589
597
|
|
|
590
598
|
result = sanitize_task(invalid_timestamp_task)
|
|
@@ -595,7 +603,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
|
|
|
595
603
|
# The sanitizer should either fix or remove the invalid timestamp
|
|
596
604
|
if sanitized.status.timestamp:
|
|
597
605
|
# If timestamp exists, it should be valid
|
|
598
|
-
datetime.fromisoformat(sanitized.status.timestamp.replace(
|
|
606
|
+
datetime.fromisoformat(sanitized.status.timestamp.replace("Z", "+00:00"))
|
|
599
607
|
|
|
600
608
|
def test_convert_valid_timestamp_strings(self):
|
|
601
609
|
"""Should convert valid timestamp strings to ISO format"""
|
|
@@ -603,10 +611,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
|
|
|
603
611
|
id="timestamp_convert",
|
|
604
612
|
contextId="ctx_convert",
|
|
605
613
|
kind="task",
|
|
606
|
-
status=A2ATaskStatus(
|
|
607
|
-
state=TaskState.WORKING,
|
|
608
|
-
timestamp="2024-01-01T12:00:00.000Z"
|
|
609
|
-
)
|
|
614
|
+
status=A2ATaskStatus(state=TaskState.WORKING, timestamp="2024-01-01T12:00:00.000Z"),
|
|
610
615
|
)
|
|
611
616
|
|
|
612
617
|
result = sanitize_task(task)
|
|
@@ -615,7 +620,7 @@ class TestSanitizeTask(TestA2ATaskSerialization):
|
|
|
615
620
|
# Should have valid ISO timestamp
|
|
616
621
|
if result.data.status.timestamp:
|
|
617
622
|
# Should be parseable as ISO datetime
|
|
618
|
-
datetime.fromisoformat(result.data.status.timestamp.replace(
|
|
623
|
+
datetime.fromisoformat(result.data.status.timestamp.replace("Z", "+00:00"))
|
|
619
624
|
|
|
620
625
|
def test_reject_fundamentally_invalid_tasks(self):
|
|
621
626
|
"""Should reject tasks that cannot be sanitized"""
|
|
@@ -644,9 +649,9 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
|
|
|
644
649
|
parts=[A2ATextPart(kind="text", text=large_text)],
|
|
645
650
|
messageId="large_msg",
|
|
646
651
|
contextId="large_ctx",
|
|
647
|
-
kind="message"
|
|
648
|
-
)
|
|
649
|
-
)
|
|
652
|
+
kind="message",
|
|
653
|
+
),
|
|
654
|
+
),
|
|
650
655
|
)
|
|
651
656
|
|
|
652
657
|
# Should handle serialization
|
|
@@ -677,9 +682,9 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
|
|
|
677
682
|
parts=[A2ADataPart(kind="data", data=nested_data)],
|
|
678
683
|
messageId="nested_msg",
|
|
679
684
|
contextId="nested_ctx",
|
|
680
|
-
kind="message"
|
|
681
|
-
)
|
|
682
|
-
)
|
|
685
|
+
kind="message",
|
|
686
|
+
),
|
|
687
|
+
),
|
|
683
688
|
)
|
|
684
689
|
|
|
685
690
|
# Should handle without errors
|
|
@@ -701,13 +706,13 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
|
|
|
701
706
|
role="agent",
|
|
702
707
|
parts=[
|
|
703
708
|
A2ATextPart(kind="text", text=special_chars),
|
|
704
|
-
A2ATextPart(kind="text", text=unicode_text)
|
|
709
|
+
A2ATextPart(kind="text", text=unicode_text),
|
|
705
710
|
],
|
|
706
711
|
messageId="unicode_msg",
|
|
707
712
|
contextId="unicode_ctx",
|
|
708
|
-
kind="message"
|
|
709
|
-
)
|
|
710
|
-
)
|
|
713
|
+
kind="message",
|
|
714
|
+
),
|
|
715
|
+
),
|
|
711
716
|
)
|
|
712
717
|
|
|
713
718
|
# Round trip should preserve special characters
|
|
@@ -734,11 +739,11 @@ class TestAdversarialScenarios(TestA2ATaskSerialization):
|
|
|
734
739
|
status=A2ATaskStatus(
|
|
735
740
|
state=TaskState.WORKING,
|
|
736
741
|
message=None, # None message
|
|
737
|
-
timestamp=None # None timestamp
|
|
742
|
+
timestamp=None, # None timestamp
|
|
738
743
|
),
|
|
739
744
|
history=None, # None history
|
|
740
745
|
artifacts=None, # None artifacts
|
|
741
|
-
metadata=None # None metadata
|
|
746
|
+
metadata=None, # None metadata
|
|
742
747
|
)
|
|
743
748
|
|
|
744
749
|
# Should handle gracefully
|