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/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
|
|
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(
|
|
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(
|
|
25
|
-
mime_type=att.get(
|
|
26
|
-
name=att.get(
|
|
27
|
-
url=att.get(
|
|
28
|
-
data=att.get(
|
|
29
|
-
format=att.get(
|
|
30
|
-
use_litellm_format=att.get(
|
|
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(
|
|
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(
|
|
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(
|
|
53
|
-
if image_context and image_context.get(
|
|
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(
|
|
56
|
-
url = image_url.get(
|
|
65
|
+
image_url = image_context.get("image_url", {})
|
|
66
|
+
url = image_url.get("url", "")
|
|
57
67
|
|
|
58
|
-
if url.startswith(
|
|
68
|
+
if url.startswith("data:"):
|
|
59
69
|
# Parse data URL: data:image/png;base64,iVBORw0KGgo...
|
|
60
|
-
header, data = url.split(
|
|
61
|
-
mime_type = header.split(
|
|
70
|
+
header, data = url.split(",", 1)
|
|
71
|
+
mime_type = header.split(":")[1].split(";")[0]
|
|
62
72
|
|
|
63
73
|
attachment = Attachment(
|
|
64
|
-
kind=
|
|
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(
|
|
103
|
+
messages = additional_context.get("messages", [])
|
|
95
104
|
if messages:
|
|
96
105
|
text_content = []
|
|
97
106
|
for msg in messages:
|
|
98
|
-
content = msg.get(
|
|
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 ==
|
|
142
|
+
if interruption.type == "tool_approval":
|
|
136
143
|
approval_value = ApprovalValue(
|
|
137
|
-
status=
|
|
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(
|
|
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 ==
|
|
198
|
+
if interruption.type == "tool_approval":
|
|
195
199
|
approval_value = ApprovalValue(
|
|
196
|
-
status=
|
|
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(
|
|
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(
|
|
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
|