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
@@ -17,6 +17,7 @@ from pydantic import BaseModel, Field
17
17
  # Standalone types for the client
18
18
  class StandaloneA2AClientConfig(BaseModel):
19
19
  """A2A client configuration"""
20
+
20
21
  model_config = {"frozen": True}
21
22
 
22
23
  base_url: str = Field(alias="baseUrl")
@@ -25,6 +26,7 @@ class StandaloneA2AClientConfig(BaseModel):
25
26
 
26
27
  class StandaloneA2AClientState(BaseModel):
27
28
  """A2A client state"""
29
+
28
30
  model_config = {"frozen": True}
29
31
 
30
32
  config: StandaloneA2AClientConfig
@@ -32,23 +34,23 @@ class StandaloneA2AClientState(BaseModel):
32
34
 
33
35
 
34
36
  # Client functions
35
- def create_standalone_a2a_client(base_url: str, config: Optional[Dict[str, Any]] = None) -> StandaloneA2AClientState:
37
+ def create_standalone_a2a_client(
38
+ base_url: str, config: Optional[Dict[str, Any]] = None
39
+ ) -> StandaloneA2AClientState:
36
40
  """Pure function to create standalone A2A client"""
37
41
  config = config or {}
38
42
 
39
43
  return StandaloneA2AClientState(
40
44
  config=StandaloneA2AClientConfig(
41
45
  baseUrl=base_url.rstrip("/"), # Remove trailing slash
42
- timeout=config.get("timeout", 30000)
46
+ timeout=config.get("timeout", 30000),
43
47
  ),
44
- sessionId=f"client_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
48
+ sessionId=f"client_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
45
49
  )
46
50
 
47
51
 
48
52
  def create_standalone_message_request(
49
- message: str,
50
- session_id: str,
51
- configuration: Optional[Dict[str, Any]] = None
53
+ message: str, session_id: str, configuration: Optional[Dict[str, Any]] = None
52
54
  ) -> Dict[str, Any]:
53
55
  """Pure function to create message request"""
54
56
  return {
@@ -61,17 +63,15 @@ def create_standalone_message_request(
61
63
  "parts": [{"kind": "text", "text": message}],
62
64
  "messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
63
65
  "contextId": session_id,
64
- "kind": "message"
66
+ "kind": "message",
65
67
  },
66
- "configuration": configuration
67
- }
68
+ "configuration": configuration,
69
+ },
68
70
  }
69
71
 
70
72
 
71
73
  def create_standalone_streaming_message_request(
72
- message: str,
73
- session_id: str,
74
- configuration: Optional[Dict[str, Any]] = None
74
+ message: str, session_id: str, configuration: Optional[Dict[str, Any]] = None
75
75
  ) -> Dict[str, Any]:
76
76
  """Pure function to create streaming message request"""
77
77
  return {
@@ -84,17 +84,15 @@ def create_standalone_streaming_message_request(
84
84
  "parts": [{"kind": "text", "text": message}],
85
85
  "messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
86
86
  "contextId": session_id,
87
- "kind": "message"
87
+ "kind": "message",
88
88
  },
89
- "configuration": configuration
90
- }
89
+ "configuration": configuration,
90
+ },
91
91
  }
92
92
 
93
93
 
94
94
  async def send_standalone_http_request(
95
- url: str,
96
- body: Dict[str, Any],
97
- timeout: int = 30000
95
+ url: str, body: Dict[str, Any], timeout: int = 30000
98
96
  ) -> Dict[str, Any]:
99
97
  """Pure function to send HTTP request"""
100
98
  timeout_seconds = timeout / 1000.0
@@ -104,10 +102,7 @@ async def send_standalone_http_request(
104
102
  response = await client.post(
105
103
  url,
106
104
  json=body,
107
- headers={
108
- "Content-Type": "application/json",
109
- "Accept": "application/json"
110
- }
105
+ headers={"Content-Type": "application/json", "Accept": "application/json"},
111
106
  )
112
107
 
113
108
  if not response.is_success:
@@ -120,8 +115,7 @@ async def send_standalone_http_request(
120
115
 
121
116
 
122
117
  async def send_standalone_a2a_request(
123
- client: StandaloneA2AClientState,
124
- request: Dict[str, Any]
118
+ client: StandaloneA2AClientState, request: Dict[str, Any]
125
119
  ) -> Dict[str, Any]:
126
120
  """Pure function to send A2A request"""
127
121
  url = f"{client.config.base_url}/a2a"
@@ -129,9 +123,7 @@ async def send_standalone_a2a_request(
129
123
 
130
124
 
131
125
  async def send_standalone_message(
132
- client: StandaloneA2AClientState,
133
- message: str,
134
- configuration: Optional[Dict[str, Any]] = None
126
+ client: StandaloneA2AClientState, message: str, configuration: Optional[Dict[str, Any]] = None
135
127
  ) -> str:
136
128
  """Pure function to send message"""
137
129
  request = create_standalone_message_request(message, client.session_id, configuration)
@@ -145,9 +137,7 @@ async def send_standalone_message(
145
137
 
146
138
 
147
139
  async def stream_standalone_message(
148
- client: StandaloneA2AClientState,
149
- message: str,
150
- configuration: Optional[Dict[str, Any]] = None
140
+ client: StandaloneA2AClientState, message: str, configuration: Optional[Dict[str, Any]] = None
151
141
  ) -> AsyncGenerator[Dict[str, Any], None]:
152
142
  """Pure function to stream message"""
153
143
  request = create_standalone_streaming_message_request(message, client.session_id, configuration)
@@ -161,12 +151,8 @@ async def stream_standalone_message(
161
151
  "POST",
162
152
  url,
163
153
  json=request,
164
- headers={
165
- "Content-Type": "application/json",
166
- "Accept": "text/event-stream"
167
- }
154
+ headers={"Content-Type": "application/json", "Accept": "text/event-stream"},
168
155
  ) as response:
169
-
170
156
  if not response.is_success:
171
157
  raise Exception(f"HTTP {response.status_code}: {response.text}")
172
158
 
@@ -197,10 +183,7 @@ async def get_standalone_agent_card(client: StandaloneA2AClientState) -> Dict[st
197
183
  url = f"{client.config.base_url}/.well-known/agent-card"
198
184
 
199
185
  async with httpx.AsyncClient(timeout=client.config.timeout / 1000.0) as http_client:
200
- response = await http_client.get(
201
- url,
202
- headers={"Accept": "application/json"}
203
- )
186
+ response = await http_client.get(url, headers={"Accept": "application/json"})
204
187
 
205
188
  if not response.is_success:
206
189
  raise Exception(f"Failed to get agent card: HTTP {response.status_code}")
@@ -212,7 +195,7 @@ async def send_standalone_message_to_agent(
212
195
  client: StandaloneA2AClientState,
213
196
  agent_name: str,
214
197
  message: str,
215
- configuration: Optional[Dict[str, Any]] = None
198
+ configuration: Optional[Dict[str, Any]] = None,
216
199
  ) -> str:
217
200
  """Pure function to send message to specific agent"""
218
201
  request = create_standalone_message_request(message, client.session_id, configuration)
@@ -288,7 +271,9 @@ async def connect_to_standalone_a2a_agent(base_url: str) -> Dict[str, Any]:
288
271
  async for event in stream_standalone_message(client, message, config):
289
272
  yield event
290
273
 
291
- async def ask_agent(agent_name: str, message: str, config: Optional[Dict[str, Any]] = None) -> str:
274
+ async def ask_agent(
275
+ agent_name: str, message: str, config: Optional[Dict[str, Any]] = None
276
+ ) -> str:
292
277
  return await send_standalone_message_to_agent(client, agent_name, message, config)
293
278
 
294
279
  return {
@@ -296,7 +281,7 @@ async def connect_to_standalone_a2a_agent(base_url: str) -> Dict[str, Any]:
296
281
  "agent_card": agent_card,
297
282
  "ask": ask,
298
283
  "stream": stream,
299
- "ask_agent": ask_agent
284
+ "ask_agent": ask_agent,
300
285
  }
301
286
 
302
287
 
@@ -308,7 +293,9 @@ async def main_example():
308
293
  connection = await connect_to_standalone_a2a_agent("http://localhost:3000")
309
294
 
310
295
  print("Connected to A2A server!")
311
- print(f"Available agents: {[skill['name'] for skill in connection['agent_card']['skills']]}")
296
+ print(
297
+ f"Available agents: {[skill['name'] for skill in connection['agent_card']['skills']]}"
298
+ )
312
299
 
313
300
  # Send a message
314
301
  response = await connection["ask"]("Hello, how can you help me?")
jaf/a2a/tests/__init__.py CHANGED
@@ -5,7 +5,7 @@ Comprehensive tests for the Agent-to-Agent Communication Protocol implementation
5
5
 
6
6
  Test Structure:
7
7
  - test_types.py: Core type validation and Pydantic model tests
8
- - test_protocol.py: JSON-RPC protocol handler tests
8
+ - test_protocol.py: JSON-RPC protocol handler tests
9
9
  - test_client.py: HTTP client functionality tests
10
10
  - test_agent.py: Agent creation and execution tests
11
11
  - test_integration.py: End-to-end integration tests
@@ -13,13 +13,13 @@ Test Structure:
13
13
  Usage:
14
14
  # Run all A2A tests
15
15
  python -m pytest jaf/a2a/tests/
16
-
16
+
17
17
  # Run specific test file
18
18
  python -m pytest jaf/a2a/tests/test_types.py
19
-
19
+
20
20
  # Run with verbose output
21
21
  python -m pytest jaf/a2a/tests/ -v
22
-
22
+
23
23
  # Run with coverage
24
24
  python -m pytest jaf/a2a/tests/ --cov=jaf.a2a
25
25
 
@@ -55,20 +55,19 @@ def run_all_tests():
55
55
  # Configure pytest for async tests
56
56
  pytest_args = [
57
57
  str(test_dir),
58
- "-v", # Verbose output
59
- "--tb=short", # Short traceback format
58
+ "-v", # Verbose output
59
+ "--tb=short", # Short traceback format
60
60
  "--asyncio-mode=auto", # Auto async mode
61
- "-x", # Stop on first failure
61
+ "-x", # Stop on first failure
62
62
  ]
63
63
 
64
64
  # Add coverage if available
65
65
  try:
66
66
  import pytest_cov
67
- pytest_args.extend([
68
- "--cov=jaf.a2a",
69
- "--cov-report=term-missing",
70
- "--cov-report=html:htmlcov"
71
- ])
67
+
68
+ pytest_args.extend(
69
+ ["--cov=jaf.a2a", "--cov-report=term-missing", "--cov-report=html:htmlcov"]
70
+ )
72
71
  except ImportError:
73
72
  print("pytest-cov not available, skipping coverage report")
74
73
 
@@ -93,12 +92,7 @@ def run_specific_test(test_name: str):
93
92
  print(f"Test file not found: {test_path}")
94
93
  return 1
95
94
 
96
- pytest_args = [
97
- str(test_path),
98
- "-v",
99
- "--tb=short",
100
- "--asyncio-mode=auto"
101
- ]
95
+ pytest_args = [str(test_path), "-v", "--tb=short", "--asyncio-mode=auto"]
102
96
 
103
97
  return pytest.main(pytest_args)
104
98
 
@@ -108,12 +102,7 @@ def run_integration_tests_only():
108
102
  test_dir = Path(__file__).parent
109
103
  integration_test = test_dir / "test_integration.py"
110
104
 
111
- pytest_args = [
112
- str(integration_test),
113
- "-v",
114
- "--tb=short",
115
- "--asyncio-mode=auto"
116
- ]
105
+ pytest_args = [str(integration_test), "-v", "--tb=short", "--asyncio-mode=auto"]
117
106
 
118
107
  return pytest.main(pytest_args)
119
108
 
@@ -127,7 +116,7 @@ def run_unit_tests_only():
127
116
  "-v",
128
117
  "--tb=short",
129
118
  "--asyncio-mode=auto",
130
- "--ignore=test_integration.py"
119
+ "--ignore=test_integration.py",
131
120
  ]
132
121
 
133
122
  return pytest.main(pytest_args)
@@ -137,14 +126,8 @@ def run_unit_tests_only():
137
126
  def pytest_configure(config):
138
127
  """Configure pytest for A2A tests"""
139
128
  # Add custom markers
140
- config.addinivalue_line(
141
- "markers",
142
- "integration: mark test as integration test"
143
- )
144
- config.addinivalue_line(
145
- "markers",
146
- "slow: mark test as slow running"
147
- )
129
+ config.addinivalue_line("markers", "integration: mark test as integration test")
130
+ config.addinivalue_line("markers", "slow: mark test as slow running")
148
131
 
149
132
 
150
133
  # Test collection
@@ -93,10 +93,7 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
93
93
  if mode == "all":
94
94
  pytest_args.append(str(test_dir))
95
95
  elif mode == "unit":
96
- pytest_args.extend([
97
- str(test_dir),
98
- "--ignore=" + str(test_dir / "test_integration.py")
99
- ])
96
+ pytest_args.extend([str(test_dir), "--ignore=" + str(test_dir / "test_integration.py")])
100
97
  elif mode == "integration":
101
98
  pytest_args.append(str(test_dir / "test_integration.py"))
102
99
  elif mode in ["types", "protocol", "client", "agent"]:
@@ -105,10 +102,7 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
105
102
  pytest_args.append(str(test_dir))
106
103
 
107
104
  # Add asyncio support
108
- pytest_args.extend([
109
- "--asyncio-mode=auto",
110
- "--tb=short"
111
- ])
105
+ pytest_args.extend(["--asyncio-mode=auto", "--tb=short"])
112
106
 
113
107
  # Verbosity
114
108
  if args.verbose:
@@ -126,6 +120,7 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
126
120
  if args.parallel:
127
121
  try:
128
122
  import pytest_xdist
123
+
129
124
  pytest_args.extend(["-n", "auto"])
130
125
  except ImportError:
131
126
  print("⚠️ pytest-xdist not available, running sequentially")
@@ -134,10 +129,8 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
134
129
  if args.coverage:
135
130
  try:
136
131
  import pytest_cov
137
- pytest_args.extend([
138
- "--cov=jaf.a2a",
139
- "--cov-report=term-missing"
140
- ])
132
+
133
+ pytest_args.extend(["--cov=jaf.a2a", "--cov-report=term-missing"])
141
134
 
142
135
  if args.html:
143
136
  pytest_args.append("--cov-report=html:htmlcov")
@@ -151,10 +144,8 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
151
144
  if args.html and not args.coverage:
152
145
  try:
153
146
  import pytest_html
154
- pytest_args.extend([
155
- "--html=test_report.html",
156
- "--self-contained-html"
157
- ])
147
+
148
+ pytest_args.extend(["--html=test_report.html", "--self-contained-html"])
158
149
  except ImportError:
159
150
  print("⚠️ pytest-html not available, skipping HTML report")
160
151
 
@@ -181,6 +172,7 @@ def run_tests(mode: str, args: argparse.Namespace) -> int:
181
172
  # Import and run pytest
182
173
  try:
183
174
  import pytest
175
+
184
176
  exit_code = pytest.main(pytest_args)
185
177
  except ImportError:
186
178
  print("❌ pytest not available")
@@ -210,7 +202,7 @@ def main():
210
202
  parser = argparse.ArgumentParser(
211
203
  description="JAF A2A Test Runner",
212
204
  formatter_class=argparse.RawDescriptionHelpFormatter,
213
- epilog=__doc__
205
+ epilog=__doc__,
214
206
  )
215
207
 
216
208
  parser.add_argument(
@@ -218,50 +210,22 @@ def main():
218
210
  nargs="?",
219
211
  default="all",
220
212
  choices=["all", "unit", "integration", "types", "protocol", "client", "agent"],
221
- help="Test mode to run (default: all)"
213
+ help="Test mode to run (default: all)",
222
214
  )
223
215
 
224
- parser.add_argument(
225
- "-v", "--verbose",
226
- action="store_true",
227
- help="Verbose output"
228
- )
216
+ parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
229
217
 
230
- parser.add_argument(
231
- "-q", "--quiet",
232
- action="store_true",
233
- help="Minimal output"
234
- )
218
+ parser.add_argument("-q", "--quiet", action="store_true", help="Minimal output")
235
219
 
236
- parser.add_argument(
237
- "--coverage",
238
- action="store_true",
239
- help="Generate coverage report"
240
- )
220
+ parser.add_argument("--coverage", action="store_true", help="Generate coverage report")
241
221
 
242
- parser.add_argument(
243
- "--html",
244
- action="store_true",
245
- help="Generate HTML reports"
246
- )
222
+ parser.add_argument("--html", action="store_true", help="Generate HTML reports")
247
223
 
248
- parser.add_argument(
249
- "--xml",
250
- action="store_true",
251
- help="Generate XML coverage report"
252
- )
224
+ parser.add_argument("--xml", action="store_true", help="Generate XML coverage report")
253
225
 
254
- parser.add_argument(
255
- "--failfast",
256
- action="store_true",
257
- help="Stop on first failure"
258
- )
226
+ parser.add_argument("--failfast", action="store_true", help="Stop on first failure")
259
227
 
260
- parser.add_argument(
261
- "--parallel",
262
- action="store_true",
263
- help="Run tests in parallel"
264
- )
228
+ parser.add_argument("--parallel", action="store_true", help="Run tests in parallel")
265
229
 
266
230
  args = parser.parse_args()
267
231