jaf-py 2.5.9__py3-none-any.whl → 2.5.11__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 +269 -210
  54. jaf/core/types.py +371 -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 +361 -280
  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.9.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.11.dist-info/RECORD +97 -0
  88. jaf_py-2.5.9.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/top_level.txt +0 -0
jaf/core/state.py CHANGED
@@ -8,7 +8,15 @@ and integrate with approval storage systems.
8
8
  from typing import Dict, Any, Optional, List
9
9
  from dataclasses import replace
10
10
 
11
- from .types import RunState, RunConfig, Interruption, ApprovalValue, Message, ContentRole, Attachment
11
+ from .types import (
12
+ RunState,
13
+ RunConfig,
14
+ Interruption,
15
+ ApprovalValue,
16
+ Message,
17
+ ContentRole,
18
+ Attachment,
19
+ )
12
20
 
13
21
 
14
22
  def _extract_attachments_from_messages(messages: List[Dict[str, Any]]) -> List[Attachment]:
@@ -16,18 +24,18 @@ def _extract_attachments_from_messages(messages: List[Dict[str, Any]]) -> List[A
16
24
  attachments = []
17
25
 
18
26
  for msg in messages:
19
- msg_attachments = msg.get('attachments', [])
27
+ msg_attachments = msg.get("attachments", [])
20
28
  for att in msg_attachments:
21
29
  try:
22
30
  # Convert dict to Attachment object
23
31
  attachment = Attachment(
24
- kind=att.get('kind', 'image'),
25
- mime_type=att.get('mime_type'),
26
- name=att.get('name'),
27
- url=att.get('url'),
28
- data=att.get('data'),
29
- format=att.get('format'),
30
- use_litellm_format=att.get('use_litellm_format')
32
+ kind=att.get("kind", "image"),
33
+ mime_type=att.get("mime_type"),
34
+ name=att.get("name"),
35
+ url=att.get("url"),
36
+ data=att.get("data"),
37
+ format=att.get("format"),
38
+ use_litellm_format=att.get("use_litellm_format"),
31
39
  )
32
40
  attachments.append(attachment)
33
41
  except Exception as e:
@@ -36,7 +44,9 @@ def _extract_attachments_from_messages(messages: List[Dict[str, Any]]) -> List[A
36
44
  return attachments
37
45
 
38
46
 
39
- def _process_additional_context_images(additional_context: Optional[Dict[str, Any]]) -> List[Attachment]:
47
+ def _process_additional_context_images(
48
+ additional_context: Optional[Dict[str, Any]],
49
+ ) -> List[Attachment]:
40
50
  """Process additional context and extract any image attachments."""
41
51
  if not additional_context:
42
52
  return []
@@ -44,27 +54,27 @@ def _process_additional_context_images(additional_context: Optional[Dict[str, An
44
54
  attachments = []
45
55
 
46
56
  # Handle messages with attachments
47
- messages = additional_context.get('messages', [])
57
+ messages = additional_context.get("messages", [])
48
58
  if messages:
49
59
  attachments.extend(_extract_attachments_from_messages(messages))
50
60
 
51
61
  # Handle legacy image_context format
52
- image_context = additional_context.get('image_context')
53
- if image_context and image_context.get('type') == 'image_url':
62
+ image_context = additional_context.get("image_context")
63
+ if image_context and image_context.get("type") == "image_url":
54
64
  try:
55
- image_url = image_context.get('image_url', {})
56
- url = image_url.get('url', '')
65
+ image_url = image_context.get("image_url", {})
66
+ url = image_url.get("url", "")
57
67
 
58
- if url.startswith('data:'):
68
+ if url.startswith("data:"):
59
69
  # Parse data URL: data:image/png;base64,iVBORw0KGgo...
60
- header, data = url.split(',', 1)
61
- mime_type = header.split(':')[1].split(';')[0]
70
+ header, data = url.split(",", 1)
71
+ mime_type = header.split(":")[1].split(";")[0]
62
72
 
63
73
  attachment = Attachment(
64
- kind='image',
74
+ kind="image",
65
75
  mime_type=mime_type,
66
76
  data=data,
67
- name=f"approval_image.{mime_type.split('/')[-1]}"
77
+ name=f"approval_image.{mime_type.split('/')[-1]}",
68
78
  )
69
79
  attachments.append(attachment)
70
80
  except Exception as e:
@@ -74,8 +84,7 @@ def _process_additional_context_images(additional_context: Optional[Dict[str, An
74
84
 
75
85
 
76
86
  def _add_approval_context_to_conversation(
77
- state: RunState[Any],
78
- additional_context: Optional[Dict[str, Any]]
87
+ state: RunState[Any], additional_context: Optional[Dict[str, Any]]
79
88
  ) -> RunState[Any]:
80
89
  """Add approval context including images to the conversation."""
81
90
  if not additional_context:
@@ -91,11 +100,11 @@ def _add_approval_context_to_conversation(
91
100
  approval_message = "Additional context provided during approval process."
92
101
 
93
102
  # Check if there are text messages to include
94
- messages = additional_context.get('messages', [])
103
+ messages = additional_context.get("messages", [])
95
104
  if messages:
96
105
  text_content = []
97
106
  for msg in messages:
98
- content = msg.get('content', '')
107
+ content = msg.get("content", "")
99
108
  if content:
100
109
  text_content.append(content)
101
110
 
@@ -104,9 +113,7 @@ def _add_approval_context_to_conversation(
104
113
 
105
114
  # Create user message with attachments (using USER role for better compatibility)
106
115
  context_message = Message(
107
- role=ContentRole.USER,
108
- content=approval_message,
109
- attachments=attachments
116
+ role=ContentRole.USER, content=approval_message, attachments=attachments
110
117
  )
111
118
 
112
119
  # Add to conversation
@@ -118,38 +125,35 @@ async def approve(
118
125
  state: RunState[Any],
119
126
  interruption: Interruption,
120
127
  additional_context: Optional[Dict[str, Any]] = None,
121
- config: Optional[RunConfig[Any]] = None
128
+ config: Optional[RunConfig[Any]] = None,
122
129
  ) -> RunState[Any]:
123
130
  """
124
131
  Approve a tool call interruption and update the run state.
125
-
132
+
126
133
  Args:
127
134
  state: Current run state
128
135
  interruption: The interruption to approve
129
136
  additional_context: Optional additional context for the approval
130
137
  config: Optional run configuration for approval storage
131
-
138
+
132
139
  Returns:
133
140
  Updated run state with approval recorded
134
141
  """
135
- if interruption.type == 'tool_approval':
142
+ if interruption.type == "tool_approval":
136
143
  approval_value = ApprovalValue(
137
- status='approved',
144
+ status="approved",
138
145
  approved=True,
139
- additional_context={
140
- **(additional_context or {}),
141
- 'status': 'approved'
142
- }
146
+ additional_context={**(additional_context or {}), "status": "approved"},
143
147
  )
144
-
148
+
145
149
  # Store in approval storage if available
146
150
  if config and config.approval_storage:
147
151
  try:
148
- print(f"[JAF:APPROVAL] Storing approval for tool_call_id {interruption.tool_call.id}: {approval_value}")
152
+ print(
153
+ f"[JAF:APPROVAL] Storing approval for tool_call_id {interruption.tool_call.id}: {approval_value}"
154
+ )
149
155
  result = await config.approval_storage.store_approval(
150
- state.run_id,
151
- interruption.tool_call.id,
152
- approval_value
156
+ state.run_id, interruption.tool_call.id, approval_value
153
157
  )
154
158
  if not result.success:
155
159
  print(f"[JAF:APPROVAL] Failed to store approval: {result.error}")
@@ -159,7 +163,7 @@ async def approve(
159
163
  except Exception as e:
160
164
  print(f"[JAF:APPROVAL] Approval storage error: {e}")
161
165
  # Continue with in-memory fallback
162
-
166
+
163
167
  # Update in-memory state
164
168
  new_approvals = {**state.approvals}
165
169
  new_approvals[interruption.tool_call.id] = approval_value
@@ -169,7 +173,7 @@ async def approve(
169
173
  updated_state = _add_approval_context_to_conversation(updated_state, additional_context)
170
174
 
171
175
  return updated_state
172
-
176
+
173
177
  return state
174
178
 
175
179
 
@@ -177,38 +181,35 @@ async def reject(
177
181
  state: RunState[Any],
178
182
  interruption: Interruption,
179
183
  additional_context: Optional[Dict[str, Any]] = None,
180
- config: Optional[RunConfig[Any]] = None
184
+ config: Optional[RunConfig[Any]] = None,
181
185
  ) -> RunState[Any]:
182
186
  """
183
187
  Reject a tool call interruption and update the run state.
184
-
188
+
185
189
  Args:
186
190
  state: Current run state
187
191
  interruption: The interruption to reject
188
192
  additional_context: Optional additional context for the rejection
189
193
  config: Optional run configuration for approval storage
190
-
194
+
191
195
  Returns:
192
196
  Updated run state with rejection recorded
193
197
  """
194
- if interruption.type == 'tool_approval':
198
+ if interruption.type == "tool_approval":
195
199
  approval_value = ApprovalValue(
196
- status='rejected',
200
+ status="rejected",
197
201
  approved=False,
198
- additional_context={
199
- **(additional_context or {}),
200
- 'status': 'rejected'
201
- }
202
+ additional_context={**(additional_context or {}), "status": "rejected"},
202
203
  )
203
-
204
+
204
205
  # Store in approval storage if available
205
206
  if config and config.approval_storage:
206
207
  try:
207
- print(f"[JAF:APPROVAL] Storing approval for tool_call_id {interruption.tool_call.id}: {approval_value}")
208
+ print(
209
+ f"[JAF:APPROVAL] Storing approval for tool_call_id {interruption.tool_call.id}: {approval_value}"
210
+ )
208
211
  result = await config.approval_storage.store_approval(
209
- state.run_id,
210
- interruption.tool_call.id,
211
- approval_value
212
+ state.run_id, interruption.tool_call.id, approval_value
212
213
  )
213
214
  if not result.success:
214
215
  print(f"[JAF:APPROVAL] Failed to store approval: {result.error}")
@@ -218,7 +219,7 @@ async def reject(
218
219
  except Exception as e:
219
220
  print(f"[JAF:APPROVAL] Approval storage error: {e}")
220
221
  # Continue with in-memory fallback
221
-
222
+
222
223
  # Update in-memory state
223
224
  new_approvals = {**state.approvals}
224
225
  new_approvals[interruption.tool_call.id] = approval_value
@@ -228,28 +229,29 @@ async def reject(
228
229
  updated_state = _add_approval_context_to_conversation(updated_state, additional_context)
229
230
 
230
231
  return updated_state
231
-
232
+
232
233
  return state
233
234
 
234
235
 
235
236
  async def load_approvals_into_state(
236
- state: RunState[Any],
237
- config: Optional[RunConfig[Any]] = None
237
+ state: RunState[Any], config: Optional[RunConfig[Any]] = None
238
238
  ) -> RunState[Any]:
239
239
  """
240
240
  Load approvals from storage into the run state.
241
-
241
+
242
242
  Args:
243
243
  state: Current run state
244
244
  config: Optional run configuration with approval storage
245
-
245
+
246
246
  Returns:
247
247
  Updated run state with loaded approvals
248
248
  """
249
249
  if not config or not config.approval_storage:
250
- print(f"[JAF:APPROVAL] No approval storage configured, using existing approvals: {state.approvals}")
250
+ print(
251
+ f"[JAF:APPROVAL] No approval storage configured, using existing approvals: {state.approvals}"
252
+ )
251
253
  return state
252
-
254
+
253
255
  try:
254
256
  print(f"[JAF:APPROVAL] Loading approvals from storage for run_id: {state.run_id}")
255
257
  result = await config.approval_storage.get_run_approvals(state.run_id)
@@ -264,4 +266,4 @@ async def load_approvals_into_state(
264
266
  return state
265
267
  except Exception as e:
266
268
  print(f"[JAF:APPROVAL] Approval loading error: {e}")
267
- return state
269
+ return state