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.
Files changed (92) hide show
  1. jaf/__init__.py +154 -57
  2. jaf/a2a/__init__.py +42 -21
  3. jaf/a2a/agent.py +79 -126
  4. jaf/a2a/agent_card.py +87 -78
  5. jaf/a2a/client.py +30 -66
  6. jaf/a2a/examples/client_example.py +12 -12
  7. jaf/a2a/examples/integration_example.py +38 -47
  8. jaf/a2a/examples/server_example.py +56 -53
  9. jaf/a2a/memory/__init__.py +0 -4
  10. jaf/a2a/memory/cleanup.py +28 -21
  11. jaf/a2a/memory/factory.py +155 -133
  12. jaf/a2a/memory/providers/composite.py +21 -26
  13. jaf/a2a/memory/providers/in_memory.py +89 -83
  14. jaf/a2a/memory/providers/postgres.py +117 -115
  15. jaf/a2a/memory/providers/redis.py +128 -121
  16. jaf/a2a/memory/serialization.py +77 -87
  17. jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
  18. jaf/a2a/memory/tests/test_cleanup.py +211 -94
  19. jaf/a2a/memory/tests/test_serialization.py +73 -68
  20. jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
  21. jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
  22. jaf/a2a/memory/types.py +91 -53
  23. jaf/a2a/protocol.py +95 -125
  24. jaf/a2a/server.py +90 -118
  25. jaf/a2a/standalone_client.py +30 -43
  26. jaf/a2a/tests/__init__.py +16 -33
  27. jaf/a2a/tests/run_tests.py +17 -53
  28. jaf/a2a/tests/test_agent.py +40 -140
  29. jaf/a2a/tests/test_client.py +54 -117
  30. jaf/a2a/tests/test_integration.py +28 -82
  31. jaf/a2a/tests/test_protocol.py +54 -139
  32. jaf/a2a/tests/test_types.py +50 -136
  33. jaf/a2a/types.py +58 -34
  34. jaf/cli.py +21 -41
  35. jaf/core/__init__.py +7 -1
  36. jaf/core/agent_tool.py +93 -72
  37. jaf/core/analytics.py +257 -207
  38. jaf/core/checkpoint.py +223 -0
  39. jaf/core/composition.py +249 -235
  40. jaf/core/engine.py +817 -519
  41. jaf/core/errors.py +55 -42
  42. jaf/core/guardrails.py +276 -202
  43. jaf/core/handoff.py +47 -31
  44. jaf/core/parallel_agents.py +69 -75
  45. jaf/core/performance.py +75 -73
  46. jaf/core/proxy.py +43 -44
  47. jaf/core/proxy_helpers.py +24 -27
  48. jaf/core/regeneration.py +220 -129
  49. jaf/core/state.py +68 -66
  50. jaf/core/streaming.py +115 -108
  51. jaf/core/tool_results.py +111 -101
  52. jaf/core/tools.py +114 -116
  53. jaf/core/tracing.py +310 -210
  54. jaf/core/types.py +403 -151
  55. jaf/core/workflows.py +209 -168
  56. jaf/exceptions.py +46 -38
  57. jaf/memory/__init__.py +1 -6
  58. jaf/memory/approval_storage.py +54 -77
  59. jaf/memory/factory.py +4 -4
  60. jaf/memory/providers/in_memory.py +216 -180
  61. jaf/memory/providers/postgres.py +216 -146
  62. jaf/memory/providers/redis.py +173 -116
  63. jaf/memory/types.py +70 -51
  64. jaf/memory/utils.py +36 -34
  65. jaf/plugins/__init__.py +12 -12
  66. jaf/plugins/base.py +105 -96
  67. jaf/policies/__init__.py +0 -1
  68. jaf/policies/handoff.py +37 -46
  69. jaf/policies/validation.py +76 -52
  70. jaf/providers/__init__.py +6 -3
  71. jaf/providers/mcp.py +97 -51
  72. jaf/providers/model.py +475 -283
  73. jaf/server/__init__.py +1 -1
  74. jaf/server/main.py +7 -11
  75. jaf/server/server.py +514 -359
  76. jaf/server/types.py +208 -52
  77. jaf/utils/__init__.py +17 -18
  78. jaf/utils/attachments.py +111 -116
  79. jaf/utils/document_processor.py +175 -174
  80. jaf/visualization/__init__.py +1 -1
  81. jaf/visualization/example.py +111 -110
  82. jaf/visualization/functional_core.py +46 -71
  83. jaf/visualization/graphviz.py +154 -189
  84. jaf/visualization/imperative_shell.py +7 -16
  85. jaf/visualization/types.py +8 -4
  86. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.12.dist-info/RECORD +97 -0
  88. jaf_py-2.5.10.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
@@ -44,10 +44,7 @@ class TestClientCreation:
44
44
 
45
45
  def test_create_a2a_client_with_config(self):
46
46
  """Test client creation with custom configuration"""
47
- config = {
48
- "timeout": 60000,
49
- "custom_field": "value"
50
- }
47
+ config = {"timeout": 60000, "custom_field": "value"}
51
48
 
52
49
  client = create_a2a_client("http://example.com/", config)
53
50
 
@@ -69,11 +66,7 @@ class TestRequestCreation:
69
66
 
70
67
  def test_create_message_request(self):
71
68
  """Test creating message requests"""
72
- request = create_message_request(
73
- "Hello, world!",
74
- "session_123",
75
- {"model": "gpt-4"}
76
- )
69
+ request = create_message_request("Hello, world!", "session_123", {"model": "gpt-4"})
77
70
 
78
71
  assert request["jsonrpc"] == "2.0"
79
72
  assert request["method"] == "message/send"
@@ -93,10 +86,7 @@ class TestRequestCreation:
93
86
 
94
87
  def test_create_message_request_no_config(self):
95
88
  """Test creating message request without configuration"""
96
- request = create_message_request(
97
- "Test message",
98
- "session_456"
99
- )
89
+ request = create_message_request("Test message", "session_456")
100
90
 
101
91
  assert request["jsonrpc"] == "2.0"
102
92
  assert request["method"] == "message/send"
@@ -106,9 +96,7 @@ class TestRequestCreation:
106
96
  def test_create_streaming_message_request(self):
107
97
  """Test creating streaming message requests"""
108
98
  request = create_streaming_message_request(
109
- "Stream this message",
110
- "session_789",
111
- {"temperature": 0.8}
99
+ "Stream this message", "session_789", {"temperature": 0.8}
112
100
  )
113
101
 
114
102
  assert request["jsonrpc"] == "2.0"
@@ -128,7 +116,7 @@ class TestHTTPRequests:
128
116
  """Test HTTP request functions"""
129
117
 
130
118
  @pytest.mark.asyncio
131
- @patch('jaf.a2a.client.httpx.AsyncClient')
119
+ @patch("jaf.a2a.client.httpx.AsyncClient")
132
120
  async def test_send_http_request_success(self, mock_client_class):
133
121
  """Test successful HTTP request"""
134
122
  # Mock response
@@ -148,20 +136,17 @@ class TestHTTPRequests:
148
136
 
149
137
  # Await the mock response if it's a coroutine
150
138
  expected_result = {"result": "success"}
151
- if hasattr(result, '__await__'):
139
+ if hasattr(result, "__await__"):
152
140
  result = await result
153
141
  assert result == expected_result
154
142
  mock_client.post.assert_called_once_with(
155
143
  url,
156
144
  json=body,
157
- headers={
158
- "Content-Type": "application/json",
159
- "Accept": "application/json"
160
- }
145
+ headers={"Content-Type": "application/json", "Accept": "application/json"},
161
146
  )
162
147
 
163
148
  @pytest.mark.asyncio
164
- @patch('jaf.a2a.client.httpx.AsyncClient')
149
+ @patch("jaf.a2a.client.httpx.AsyncClient")
165
150
  async def test_send_http_request_http_error(self, mock_client_class):
166
151
  """Test HTTP request with error status"""
167
152
  # Mock error response
@@ -181,7 +166,7 @@ class TestHTTPRequests:
181
166
  assert "HTTP 404: Not Found" in str(exc_info.value)
182
167
 
183
168
  @pytest.mark.asyncio
184
- @patch('jaf.a2a.client.httpx.AsyncClient')
169
+ @patch("jaf.a2a.client.httpx.AsyncClient")
185
170
  async def test_send_http_request_timeout(self, mock_client_class):
186
171
  """Test HTTP request timeout"""
187
172
  # Mock client that raises timeout
@@ -200,17 +185,13 @@ class TestHTTPRequests:
200
185
  client = create_a2a_client("http://localhost:3000")
201
186
  request = {"test": "request"}
202
187
 
203
- with patch('jaf.a2a.client.send_http_request') as mock_send:
188
+ with patch("jaf.a2a.client.send_http_request") as mock_send:
204
189
  mock_send.return_value = {"result": "success"}
205
190
 
206
191
  result = await send_a2a_request(client, request)
207
192
 
208
193
  assert result == {"result": "success"}
209
- mock_send.assert_called_once_with(
210
- "http://localhost:3000/a2a",
211
- request,
212
- 30000
213
- )
194
+ mock_send.assert_called_once_with("http://localhost:3000/a2a", request, 30000)
214
195
 
215
196
 
216
197
  class TestMessageSending:
@@ -227,13 +208,11 @@ class TestMessageSending:
227
208
  "id": "req_123",
228
209
  "result": {
229
210
  "kind": "task",
230
- "artifacts": [{
231
- "parts": [{"kind": "text", "text": "Hello back!"}]
232
- }]
233
- }
211
+ "artifacts": [{"parts": [{"kind": "text", "text": "Hello back!"}]}],
212
+ },
234
213
  }
235
214
 
236
- with patch('jaf.a2a.client.send_a2a_request') as mock_send:
215
+ with patch("jaf.a2a.client.send_a2a_request") as mock_send:
237
216
  mock_send.return_value = mock_response
238
217
 
239
218
  result = await send_message(client, "Hello!", {"model": "gpt-4"})
@@ -250,13 +229,10 @@ class TestMessageSending:
250
229
  mock_response = {
251
230
  "jsonrpc": "2.0",
252
231
  "id": "req_123",
253
- "error": {
254
- "code": -32603,
255
- "message": "Internal error"
256
- }
232
+ "error": {"code": -32603, "message": "Internal error"},
257
233
  }
258
234
 
259
- with patch('jaf.a2a.client.send_a2a_request') as mock_send:
235
+ with patch("jaf.a2a.client.send_a2a_request") as mock_send:
260
236
  mock_send.return_value = mock_response
261
237
 
262
238
  with pytest.raises(Exception) as exc_info:
@@ -269,18 +245,12 @@ class TestMessageSending:
269
245
  """Test sending message to specific agent"""
270
246
  client = create_a2a_client("http://localhost:3000")
271
247
 
272
- mock_response = {
273
- "jsonrpc": "2.0",
274
- "id": "req_123",
275
- "result": "Agent response"
276
- }
248
+ mock_response = {"jsonrpc": "2.0", "id": "req_123", "result": "Agent response"}
277
249
 
278
- with patch('jaf.a2a.client.send_http_request') as mock_send:
250
+ with patch("jaf.a2a.client.send_http_request") as mock_send:
279
251
  mock_send.return_value = mock_response
280
252
 
281
- result = await send_message_to_agent(
282
- client, "TestAgent", "Hello agent!"
283
- )
253
+ result = await send_message_to_agent(client, "TestAgent", "Hello agent!")
284
254
 
285
255
  assert result == "Agent response"
286
256
  mock_send.assert_called_once()
@@ -294,7 +264,7 @@ class TestStreaming:
294
264
  """Test streaming functionality"""
295
265
 
296
266
  @pytest.mark.asyncio
297
- @patch('jaf.a2a.client.stream_message')
267
+ @patch("jaf.a2a.client.stream_message")
298
268
  async def test_stream_message_success(self, mock_stream_message):
299
269
  """Test successful message streaming"""
300
270
  client = create_a2a_client("http://localhost:3000")
@@ -316,7 +286,7 @@ class TestStreaming:
316
286
  mock_stream_message.assert_called_once_with(client, "Stream test")
317
287
 
318
288
  @pytest.mark.asyncio
319
- @patch('jaf.a2a.client.stream_message_to_agent')
289
+ @patch("jaf.a2a.client.stream_message_to_agent")
320
290
  async def test_stream_message_to_agent(self, mock_stream_message_to_agent):
321
291
  """Test streaming message to specific agent"""
322
292
  client = create_a2a_client("http://localhost:3000")
@@ -328,9 +298,7 @@ class TestStreaming:
328
298
  mock_stream_message_to_agent.side_effect = mock_stream_events
329
299
 
330
300
  events = []
331
- async for event in mock_stream_message_to_agent(
332
- client, "TestAgent", "Stream to agent"
333
- ):
301
+ async for event in mock_stream_message_to_agent(client, "TestAgent", "Stream to agent"):
334
302
  events.append(event)
335
303
 
336
304
  assert len(events) == 1
@@ -342,7 +310,7 @@ class TestAgentDiscovery:
342
310
  """Test agent discovery functions"""
343
311
 
344
312
  @pytest.mark.asyncio
345
- @patch('jaf.a2a.client.httpx.AsyncClient')
313
+ @patch("jaf.a2a.client.httpx.AsyncClient")
346
314
  async def test_get_agent_card(self, mock_client_class):
347
315
  """Test getting agent card"""
348
316
  client = create_a2a_client("http://localhost:3000")
@@ -351,7 +319,7 @@ class TestAgentDiscovery:
351
319
  "protocolVersion": "0.3.0",
352
320
  "name": "Test Agent",
353
321
  "description": "A test agent",
354
- "skills": []
322
+ "skills": [],
355
323
  }
356
324
 
357
325
  # Mock response
@@ -367,23 +335,19 @@ class TestAgentDiscovery:
367
335
  result = await get_agent_card(client)
368
336
 
369
337
  # Handle mock coroutine if needed
370
- if hasattr(result, '__await__'):
338
+ if hasattr(result, "__await__"):
371
339
  result = await result
372
340
  assert result == mock_agent_card
373
341
  mock_client.get.assert_called_once_with(
374
- "http://localhost:3000/.well-known/agent-card",
375
- headers={"Accept": "application/json"}
342
+ "http://localhost:3000/.well-known/agent-card", headers={"Accept": "application/json"}
376
343
  )
377
344
 
378
345
  @pytest.mark.asyncio
379
346
  async def test_discover_agents(self):
380
347
  """Test discover agents convenience function"""
381
- mock_agent_card = {
382
- "name": "Test Server",
383
- "skills": [{"name": "skill1"}]
384
- }
348
+ mock_agent_card = {"name": "Test Server", "skills": [{"name": "skill1"}]}
385
349
 
386
- with patch('jaf.a2a.client.get_agent_card') as mock_get_card:
350
+ with patch("jaf.a2a.client.get_agent_card") as mock_get_card:
387
351
  mock_get_card.return_value = mock_agent_card
388
352
 
389
353
  result = await discover_agents("http://localhost:3000")
@@ -396,16 +360,12 @@ class TestHealthAndCapabilities:
396
360
  """Test health and capabilities functions"""
397
361
 
398
362
  @pytest.mark.asyncio
399
- @patch('jaf.a2a.client.httpx.AsyncClient')
363
+ @patch("jaf.a2a.client.httpx.AsyncClient")
400
364
  async def test_check_a2a_health(self, mock_client_class):
401
365
  """Test health check"""
402
366
  client = create_a2a_client("http://localhost:3000")
403
367
 
404
- mock_health = {
405
- "status": "healthy",
406
- "protocol": "A2A",
407
- "version": "0.3.0"
408
- }
368
+ mock_health = {"status": "healthy", "protocol": "A2A", "version": "0.3.0"}
409
369
 
410
370
  # Mock response
411
371
  mock_response = AsyncMock()
@@ -420,23 +380,22 @@ class TestHealthAndCapabilities:
420
380
  result = await check_a2a_health(client)
421
381
 
422
382
  # Handle mock coroutine if needed
423
- if hasattr(result, '__await__'):
383
+ if hasattr(result, "__await__"):
424
384
  result = await result
425
385
  assert result == mock_health
426
386
  mock_client.get.assert_called_once_with(
427
- "http://localhost:3000/a2a/health",
428
- headers={"Accept": "application/json"}
387
+ "http://localhost:3000/a2a/health", headers={"Accept": "application/json"}
429
388
  )
430
389
 
431
390
  @pytest.mark.asyncio
432
- @patch('jaf.a2a.client.httpx.AsyncClient')
391
+ @patch("jaf.a2a.client.httpx.AsyncClient")
433
392
  async def test_get_a2a_capabilities(self, mock_client_class):
434
393
  """Test getting capabilities"""
435
394
  client = create_a2a_client("http://localhost:3000")
436
395
 
437
396
  mock_capabilities = {
438
397
  "supportedMethods": ["message/send", "message/stream"],
439
- "supportedTransports": ["JSONRPC"]
398
+ "supportedTransports": ["JSONRPC"],
440
399
  }
441
400
 
442
401
  # Mock response
@@ -452,12 +411,11 @@ class TestHealthAndCapabilities:
452
411
  result = await get_a2a_capabilities(client)
453
412
 
454
413
  # Handle mock coroutine if needed
455
- if hasattr(result, '__await__'):
414
+ if hasattr(result, "__await__"):
456
415
  result = await result
457
416
  assert result == mock_capabilities
458
417
  mock_client.get.assert_called_once_with(
459
- "http://localhost:3000/a2a/capabilities",
460
- headers={"Accept": "application/json"}
418
+ "http://localhost:3000/a2a/capabilities", headers={"Accept": "application/json"}
461
419
  )
462
420
 
463
421
 
@@ -473,20 +431,16 @@ class TestResponseProcessing:
473
431
  """Test extracting text from task response"""
474
432
  task_response = {
475
433
  "kind": "task",
476
- "artifacts": [{
477
- "parts": [
478
- {"kind": "text", "text": "First text part"},
479
- {"kind": "data", "data": {"key": "value"}},
480
- {"kind": "text", "text": "Second text part"}
481
- ]
482
- }],
483
- "history": [
434
+ "artifacts": [
484
435
  {
485
436
  "parts": [
486
- {"kind": "text", "text": "History message"}
437
+ {"kind": "text", "text": "First text part"},
438
+ {"kind": "data", "data": {"key": "value"}},
439
+ {"kind": "text", "text": "Second text part"},
487
440
  ]
488
441
  }
489
- ]
442
+ ],
443
+ "history": [{"parts": [{"kind": "text", "text": "History message"}]}],
490
444
  }
491
445
 
492
446
  result = extract_text_response(task_response)
@@ -501,10 +455,10 @@ class TestResponseProcessing:
501
455
  {
502
456
  "parts": [
503
457
  {"kind": "text", "text": "History text 1"},
504
- {"kind": "text", "text": "History text 2"}
458
+ {"kind": "text", "text": "History text 2"},
505
459
  ]
506
460
  }
507
- ]
461
+ ],
508
462
  }
509
463
 
510
464
  result = extract_text_response(task_response)
@@ -516,8 +470,8 @@ class TestResponseProcessing:
516
470
  "kind": "message",
517
471
  "parts": [
518
472
  {"kind": "text", "text": "Message part 1"},
519
- {"kind": "text", "text": "Message part 2"}
520
- ]
473
+ {"kind": "text", "text": "Message part 2"},
474
+ ],
521
475
  }
522
476
 
523
477
  result = extract_text_response(message_response)
@@ -546,11 +500,7 @@ class TestUtilityFunctions:
546
500
 
547
501
  def test_create_a2a_message_dict(self):
548
502
  """Test creating A2A message dictionary"""
549
- message = create_a2a_message_dict(
550
- "Hello world",
551
- "agent",
552
- "ctx_123"
553
- )
503
+ message = create_a2a_message_dict("Hello world", "agent", "ctx_123")
554
504
 
555
505
  assert message["role"] == "agent"
556
506
  assert message["parts"][0]["kind"] == "text"
@@ -589,11 +539,7 @@ class TestUtilityFunctions:
589
539
 
590
540
  def test_validate_a2a_response_valid(self):
591
541
  """Test validating valid A2A responses"""
592
- valid_response = {
593
- "jsonrpc": "2.0",
594
- "id": "req_123",
595
- "result": {"data": "success"}
596
- }
542
+ valid_response = {"jsonrpc": "2.0", "id": "req_123", "result": {"data": "success"}}
597
543
 
598
544
  assert validate_a2a_response(valid_response) is True
599
545
 
@@ -601,7 +547,7 @@ class TestUtilityFunctions:
601
547
  valid_error = {
602
548
  "jsonrpc": "2.0",
603
549
  "id": "req_123",
604
- "error": {"code": -32603, "message": "Error"}
550
+ "error": {"code": -32603, "message": "Error"},
605
551
  }
606
552
 
607
553
  assert validate_a2a_response(valid_error) is True
@@ -612,19 +558,13 @@ class TestUtilityFunctions:
612
558
  assert validate_a2a_response({"id": "req_123", "result": {}}) is False
613
559
 
614
560
  # Wrong jsonrpc version
615
- assert validate_a2a_response({
616
- "jsonrpc": "1.0", "id": "req_123", "result": {}
617
- }) is False
561
+ assert validate_a2a_response({"jsonrpc": "1.0", "id": "req_123", "result": {}}) is False
618
562
 
619
563
  # Missing id
620
- assert validate_a2a_response({
621
- "jsonrpc": "2.0", "result": {}
622
- }) is False
564
+ assert validate_a2a_response({"jsonrpc": "2.0", "result": {}}) is False
623
565
 
624
566
  # Missing both result and error
625
- assert validate_a2a_response({
626
- "jsonrpc": "2.0", "id": "req_123"
627
- }) is False
567
+ assert validate_a2a_response({"jsonrpc": "2.0", "id": "req_123"}) is False
628
568
 
629
569
  # Not a dict
630
570
  assert validate_a2a_response("not a dict") is False
@@ -636,12 +576,9 @@ class TestConvenienceConnection:
636
576
  @pytest.mark.asyncio
637
577
  async def test_connect_to_a2a_agent(self):
638
578
  """Test convenience connection to A2A agent"""
639
- mock_agent_card = {
640
- "name": "Test Agent",
641
- "description": "Test description"
642
- }
579
+ mock_agent_card = {"name": "Test Agent", "description": "Test description"}
643
580
 
644
- with patch('jaf.a2a.client.get_agent_card') as mock_get_card:
581
+ with patch("jaf.a2a.client.get_agent_card") as mock_get_card:
645
582
  mock_get_card.return_value = mock_agent_card
646
583
 
647
584
  connection = await connect_to_a2a_agent("http://localhost:3000")