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/a2a/agent.py
CHANGED
|
@@ -27,7 +27,7 @@ def create_a2a_agent(
|
|
|
27
27
|
description: str,
|
|
28
28
|
instruction: str,
|
|
29
29
|
tools: List[A2AAgentTool],
|
|
30
|
-
supported_content_types: Optional[List[str]] = None
|
|
30
|
+
supported_content_types: Optional[List[str]] = None,
|
|
31
31
|
) -> A2AAgent:
|
|
32
32
|
"""Pure function to create A2A compatible agent"""
|
|
33
33
|
return A2AAgent(
|
|
@@ -35,7 +35,7 @@ def create_a2a_agent(
|
|
|
35
35
|
description=description,
|
|
36
36
|
supportedContentTypes=supported_content_types or ["text/plain", "application/json"],
|
|
37
37
|
instruction=instruction,
|
|
38
|
-
tools=tools
|
|
38
|
+
tools=tools,
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
|
|
@@ -43,14 +43,11 @@ def create_a2a_tool(
|
|
|
43
43
|
name: str,
|
|
44
44
|
description: str,
|
|
45
45
|
parameters: Dict[str, Any],
|
|
46
|
-
execute_func: Callable[[Any, Optional[ToolContext]], Any]
|
|
46
|
+
execute_func: Callable[[Any, Optional[ToolContext]], Any],
|
|
47
47
|
) -> A2AAgentTool:
|
|
48
48
|
"""Pure function to create A2A tool"""
|
|
49
49
|
return A2AAgentTool(
|
|
50
|
-
name=name,
|
|
51
|
-
description=description,
|
|
52
|
-
parameters=parameters,
|
|
53
|
-
execute=execute_func
|
|
50
|
+
name=name, description=description, parameters=parameters, execute=execute_func
|
|
54
51
|
)
|
|
55
52
|
|
|
56
53
|
|
|
@@ -66,7 +63,7 @@ def create_initial_agent_state(session_id: str) -> AgentState:
|
|
|
66
63
|
messages=[],
|
|
67
64
|
context={},
|
|
68
65
|
artifacts=[],
|
|
69
|
-
timestamp=datetime.now().isoformat()
|
|
66
|
+
timestamp=datetime.now().isoformat(),
|
|
70
67
|
)
|
|
71
68
|
|
|
72
69
|
|
|
@@ -77,14 +74,14 @@ def add_message_to_state(state: AgentState, message: Any) -> AgentState:
|
|
|
77
74
|
messages=[*state.messages, message],
|
|
78
75
|
context=state.context,
|
|
79
76
|
artifacts=state.artifacts,
|
|
80
|
-
timestamp=datetime.now().isoformat()
|
|
77
|
+
timestamp=datetime.now().isoformat(),
|
|
81
78
|
)
|
|
82
79
|
|
|
83
80
|
|
|
84
81
|
def update_state_from_run_result(state: AgentState, outcome: Any) -> AgentState:
|
|
85
82
|
"""Pure function to update state from run result"""
|
|
86
83
|
new_artifacts = state.artifacts
|
|
87
|
-
if hasattr(outcome,
|
|
84
|
+
if hasattr(outcome, "artifacts") and outcome.artifacts:
|
|
88
85
|
new_artifacts = [*state.artifacts, *outcome.artifacts]
|
|
89
86
|
|
|
90
87
|
return AgentState(
|
|
@@ -92,7 +89,7 @@ def update_state_from_run_result(state: AgentState, outcome: Any) -> AgentState:
|
|
|
92
89
|
messages=state.messages,
|
|
93
90
|
context=state.context,
|
|
94
91
|
artifacts=new_artifacts,
|
|
95
|
-
timestamp=datetime.now().isoformat()
|
|
92
|
+
timestamp=datetime.now().isoformat(),
|
|
96
93
|
)
|
|
97
94
|
|
|
98
95
|
|
|
@@ -110,24 +107,16 @@ def transform_a2a_agent_to_jaf(a2a_agent: A2AAgent) -> Agent:
|
|
|
110
107
|
# Transform tools using functional approach
|
|
111
108
|
jaf_tools = [transform_a2a_tool_to_jaf(a2a_tool) for a2a_tool in a2a_agent.tools]
|
|
112
109
|
|
|
113
|
-
return Agent(
|
|
114
|
-
name=a2a_agent.name,
|
|
115
|
-
instructions=instructions_func,
|
|
116
|
-
tools=jaf_tools
|
|
117
|
-
)
|
|
110
|
+
return Agent(name=a2a_agent.name, instructions=instructions_func, tools=jaf_tools)
|
|
118
111
|
|
|
119
112
|
|
|
120
|
-
def transform_a2a_tool_to_jaf(a2a_tool: A2AAgentTool) ->
|
|
113
|
+
def transform_a2a_tool_to_jaf(a2a_tool: A2AAgentTool) -> "JAFToolImplementation":
|
|
121
114
|
"""Pure function to transform A2A tool to JAF tool"""
|
|
122
115
|
|
|
123
116
|
async def execute_wrapper(args: Any, context: Any) -> Any:
|
|
124
117
|
tool_context = ToolContext(
|
|
125
|
-
actions={
|
|
126
|
-
|
|
127
|
-
"skipSummarization": False,
|
|
128
|
-
"escalate": False
|
|
129
|
-
},
|
|
130
|
-
metadata=context or {}
|
|
118
|
+
actions={"requiresInput": False, "skipSummarization": False, "escalate": False},
|
|
119
|
+
metadata=context or {},
|
|
131
120
|
)
|
|
132
121
|
|
|
133
122
|
result = await a2a_tool.execute(args, tool_context)
|
|
@@ -140,16 +129,12 @@ def transform_a2a_tool_to_jaf(a2a_tool: A2AAgentTool) -> 'JAFToolImplementation'
|
|
|
140
129
|
|
|
141
130
|
# Create ToolSchema object
|
|
142
131
|
from ..core.types import ToolSchema
|
|
132
|
+
|
|
143
133
|
tool_schema = ToolSchema(
|
|
144
|
-
name=a2a_tool.name,
|
|
145
|
-
description=a2a_tool.description,
|
|
146
|
-
parameters=a2a_tool.parameters
|
|
134
|
+
name=a2a_tool.name, description=a2a_tool.description, parameters=a2a_tool.parameters
|
|
147
135
|
)
|
|
148
136
|
|
|
149
|
-
return JAFToolImplementation(
|
|
150
|
-
schema=tool_schema,
|
|
151
|
-
execute=execute_wrapper
|
|
152
|
-
)
|
|
137
|
+
return JAFToolImplementation(schema=tool_schema, execute=execute_wrapper)
|
|
153
138
|
|
|
154
139
|
|
|
155
140
|
class JAFToolImplementation:
|
|
@@ -170,21 +155,18 @@ class JAFToolImplementation:
|
|
|
170
155
|
return await self._execute(args, context)
|
|
171
156
|
|
|
172
157
|
|
|
173
|
-
def create_run_config_for_a2a_agent(
|
|
174
|
-
a2a_agent: A2AAgent,
|
|
175
|
-
model_provider: Any
|
|
176
|
-
) -> RunConfig:
|
|
158
|
+
def create_run_config_for_a2a_agent(a2a_agent: A2AAgent, model_provider: Any) -> RunConfig:
|
|
177
159
|
"""Pure function to create run configuration for A2A agent"""
|
|
178
160
|
jaf_agent = transform_a2a_agent_to_jaf(a2a_agent)
|
|
179
161
|
|
|
180
162
|
def event_handler(event: Any) -> None:
|
|
181
163
|
# Handle different event types properly
|
|
182
|
-
if hasattr(event,
|
|
164
|
+
if hasattr(event, "data"):
|
|
183
165
|
event_data = event.data
|
|
184
166
|
event_type = type(event).__name__
|
|
185
167
|
elif isinstance(event, dict):
|
|
186
|
-
event_data = event.get(
|
|
187
|
-
event_type = event.get(
|
|
168
|
+
event_data = event.get("data", "")
|
|
169
|
+
event_type = event.get("type", "unknown")
|
|
188
170
|
else:
|
|
189
171
|
event_data = str(event)
|
|
190
172
|
event_type = type(event).__name__
|
|
@@ -195,21 +177,19 @@ def create_run_config_for_a2a_agent(
|
|
|
195
177
|
agent_registry={a2a_agent.name: jaf_agent},
|
|
196
178
|
model_provider=model_provider,
|
|
197
179
|
max_turns=10,
|
|
198
|
-
on_event=event_handler
|
|
180
|
+
on_event=event_handler,
|
|
199
181
|
)
|
|
200
182
|
|
|
201
183
|
|
|
202
184
|
def transform_to_run_state(
|
|
203
|
-
state: AgentState,
|
|
204
|
-
agent_name: str,
|
|
205
|
-
context: Optional[Dict[str, Any]] = None
|
|
185
|
+
state: AgentState, agent_name: str, context: Optional[Dict[str, Any]] = None
|
|
206
186
|
) -> RunState:
|
|
207
187
|
"""Pure function to transform agent state to JAF run state"""
|
|
208
188
|
from ..core.types import generate_run_id, generate_trace_id
|
|
209
189
|
|
|
210
190
|
# Convert A2A messages to JAF Message format
|
|
211
191
|
jaf_messages = [
|
|
212
|
-
create_user_message(msg.content if hasattr(msg,
|
|
192
|
+
create_user_message(msg.content if hasattr(msg, "content") else str(msg))
|
|
213
193
|
for msg in state.messages
|
|
214
194
|
if msg is not None
|
|
215
195
|
]
|
|
@@ -220,15 +200,12 @@ def transform_to_run_state(
|
|
|
220
200
|
messages=jaf_messages,
|
|
221
201
|
current_agent_name=agent_name,
|
|
222
202
|
context=context or {},
|
|
223
|
-
turn_count=0
|
|
203
|
+
turn_count=0,
|
|
224
204
|
)
|
|
225
205
|
|
|
226
206
|
|
|
227
207
|
async def process_agent_query(
|
|
228
|
-
agent: A2AAgent,
|
|
229
|
-
query: str,
|
|
230
|
-
state: AgentState,
|
|
231
|
-
model_provider: Any
|
|
208
|
+
agent: A2AAgent, query: str, state: AgentState, model_provider: Any
|
|
232
209
|
) -> AsyncGenerator[StreamEvent, None]:
|
|
233
210
|
"""Pure async generator function to process agent query with improved streaming"""
|
|
234
211
|
start_time = time.time()
|
|
@@ -248,7 +225,7 @@ async def process_agent_query(
|
|
|
248
225
|
new_state=new_state.model_dump(),
|
|
249
226
|
timestamp=datetime.now().isoformat(),
|
|
250
227
|
updates="Initializing agent state",
|
|
251
|
-
metrics={"start_time": start_time, "status": "starting"}
|
|
228
|
+
metrics={"start_time": start_time, "status": "starting"},
|
|
252
229
|
)
|
|
253
230
|
|
|
254
231
|
try:
|
|
@@ -266,19 +243,19 @@ async def process_agent_query(
|
|
|
266
243
|
metrics={
|
|
267
244
|
"start_time": start_time,
|
|
268
245
|
"processing_time": processing_time,
|
|
269
|
-
"status": "processing"
|
|
270
|
-
}
|
|
246
|
+
"status": "processing",
|
|
247
|
+
},
|
|
271
248
|
)
|
|
272
249
|
|
|
273
250
|
# Execute JAF engine (pure function)
|
|
274
251
|
result = await run(run_state, run_config)
|
|
275
252
|
completion_time = time.time()
|
|
276
253
|
|
|
277
|
-
if hasattr(result.outcome,
|
|
254
|
+
if hasattr(result.outcome, "status") and result.outcome.status == "completed":
|
|
278
255
|
final_state = update_state_from_run_result(new_state, result.outcome)
|
|
279
256
|
yield StreamEvent(
|
|
280
257
|
isTaskComplete=True,
|
|
281
|
-
content=getattr(result.outcome,
|
|
258
|
+
content=getattr(result.outcome, "output", "Task completed"),
|
|
282
259
|
new_state=final_state.model_dump(),
|
|
283
260
|
timestamp=datetime.now().isoformat(),
|
|
284
261
|
updates="Task completed successfully",
|
|
@@ -286,12 +263,12 @@ async def process_agent_query(
|
|
|
286
263
|
"start_time": start_time,
|
|
287
264
|
"completion_time": completion_time,
|
|
288
265
|
"total_duration": completion_time - start_time,
|
|
289
|
-
"status": "completed"
|
|
290
|
-
}
|
|
266
|
+
"status": "completed",
|
|
267
|
+
},
|
|
291
268
|
)
|
|
292
269
|
else:
|
|
293
270
|
final_state = update_state_from_run_result(new_state, result.outcome)
|
|
294
|
-
error_content = getattr(result.outcome,
|
|
271
|
+
error_content = getattr(result.outcome, "error", "Unknown error")
|
|
295
272
|
yield StreamEvent(
|
|
296
273
|
isTaskComplete=True,
|
|
297
274
|
content=f"Error: {json.dumps(error_content) if isinstance(error_content, dict) else str(error_content)}",
|
|
@@ -302,14 +279,15 @@ async def process_agent_query(
|
|
|
302
279
|
"start_time": start_time,
|
|
303
280
|
"completion_time": completion_time,
|
|
304
281
|
"total_duration": completion_time - start_time,
|
|
305
|
-
"status": "error"
|
|
306
|
-
}
|
|
282
|
+
"status": "error",
|
|
283
|
+
},
|
|
307
284
|
)
|
|
308
285
|
except Exception as error:
|
|
309
286
|
error_time = time.time()
|
|
310
287
|
|
|
311
288
|
# Log the actual error for debugging but don't expose internal details
|
|
312
289
|
import logging
|
|
290
|
+
|
|
313
291
|
logging.error(f"Agent execution error for {agent.name}: {error!s}", exc_info=True)
|
|
314
292
|
|
|
315
293
|
# Determine error type and provide safe error message
|
|
@@ -335,8 +313,8 @@ async def process_agent_query(
|
|
|
335
313
|
"error_time": error_time,
|
|
336
314
|
"total_duration": error_time - start_time,
|
|
337
315
|
"status": "failed",
|
|
338
|
-
"error_type": type(error).__name__
|
|
339
|
-
}
|
|
316
|
+
"error_type": type(error).__name__,
|
|
317
|
+
},
|
|
340
318
|
)
|
|
341
319
|
|
|
342
320
|
|
|
@@ -346,19 +324,13 @@ def extract_text_from_a2a_message(message: Dict[str, Any]) -> str:
|
|
|
346
324
|
return ""
|
|
347
325
|
|
|
348
326
|
# Use functional approach instead of mutation
|
|
349
|
-
text_parts = [
|
|
350
|
-
part.get("text", "")
|
|
351
|
-
for part in message["parts"]
|
|
352
|
-
if part.get("kind") == "text"
|
|
353
|
-
]
|
|
327
|
+
text_parts = [part.get("text", "") for part in message["parts"] if part.get("kind") == "text"]
|
|
354
328
|
|
|
355
329
|
return "\n".join(text_parts)
|
|
356
330
|
|
|
357
331
|
|
|
358
332
|
def create_a2a_text_message(
|
|
359
|
-
text: str,
|
|
360
|
-
context_id: str,
|
|
361
|
-
task_id: Optional[str] = None
|
|
333
|
+
text: str, context_id: str, task_id: Optional[str] = None
|
|
362
334
|
) -> Dict[str, Any]:
|
|
363
335
|
"""Pure function to create A2A text message"""
|
|
364
336
|
return {
|
|
@@ -368,14 +340,12 @@ def create_a2a_text_message(
|
|
|
368
340
|
"contextId": context_id,
|
|
369
341
|
"taskId": task_id,
|
|
370
342
|
"kind": "message",
|
|
371
|
-
"timestamp": datetime.now().isoformat()
|
|
343
|
+
"timestamp": datetime.now().isoformat(),
|
|
372
344
|
}
|
|
373
345
|
|
|
374
346
|
|
|
375
347
|
def create_a2a_data_message(
|
|
376
|
-
data: Dict[str, Any],
|
|
377
|
-
context_id: str,
|
|
378
|
-
task_id: Optional[str] = None
|
|
348
|
+
data: Dict[str, Any], context_id: str, task_id: Optional[str] = None
|
|
379
349
|
) -> Dict[str, Any]:
|
|
380
350
|
"""Pure function to create A2A data message"""
|
|
381
351
|
return {
|
|
@@ -385,14 +355,11 @@ def create_a2a_data_message(
|
|
|
385
355
|
"contextId": context_id,
|
|
386
356
|
"taskId": task_id,
|
|
387
357
|
"kind": "message",
|
|
388
|
-
"timestamp": datetime.now().isoformat()
|
|
358
|
+
"timestamp": datetime.now().isoformat(),
|
|
389
359
|
}
|
|
390
360
|
|
|
391
361
|
|
|
392
|
-
def create_a2a_task(
|
|
393
|
-
message: Dict[str, Any],
|
|
394
|
-
context_id: Optional[str] = None
|
|
395
|
-
) -> Dict[str, Any]:
|
|
362
|
+
def create_a2a_task(message: Dict[str, Any], context_id: Optional[str] = None) -> Dict[str, Any]:
|
|
396
363
|
"""Pure function to create A2A task"""
|
|
397
364
|
task_id = f"task_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
|
|
398
365
|
current_time = datetime.now().isoformat()
|
|
@@ -400,42 +367,35 @@ def create_a2a_task(
|
|
|
400
367
|
return {
|
|
401
368
|
"id": task_id,
|
|
402
369
|
"contextId": context_id or f"ctx_{int(time.time() * 1000)}",
|
|
403
|
-
"status": {
|
|
404
|
-
"state": "submitted",
|
|
405
|
-
"timestamp": current_time
|
|
406
|
-
},
|
|
370
|
+
"status": {"state": "submitted", "timestamp": current_time},
|
|
407
371
|
"history": [message],
|
|
408
372
|
"artifacts": [],
|
|
409
|
-
"kind": "task"
|
|
373
|
+
"kind": "task",
|
|
410
374
|
}
|
|
411
375
|
|
|
412
376
|
|
|
413
377
|
def update_a2a_task_status(
|
|
414
|
-
task: Dict[str, Any],
|
|
415
|
-
state: str,
|
|
416
|
-
message: Optional[Dict[str, Any]] = None
|
|
378
|
+
task: Dict[str, Any], state: str, message: Optional[Dict[str, Any]] = None
|
|
417
379
|
) -> Dict[str, Any]:
|
|
418
380
|
"""Pure function to update A2A task status"""
|
|
419
381
|
updated_task = task.copy()
|
|
420
382
|
updated_task["status"] = {
|
|
421
383
|
"state": state,
|
|
422
384
|
"message": message,
|
|
423
|
-
"timestamp": datetime.now().isoformat()
|
|
385
|
+
"timestamp": datetime.now().isoformat(),
|
|
424
386
|
}
|
|
425
387
|
return updated_task
|
|
426
388
|
|
|
427
389
|
|
|
428
390
|
def add_artifact_to_a2a_task(
|
|
429
|
-
task: Dict[str, Any],
|
|
430
|
-
parts: List[Dict[str, Any]],
|
|
431
|
-
name: str
|
|
391
|
+
task: Dict[str, Any], parts: List[Dict[str, Any]], name: str
|
|
432
392
|
) -> Dict[str, Any]:
|
|
433
393
|
"""Pure function to add artifact to A2A task"""
|
|
434
394
|
artifact = {
|
|
435
395
|
"artifactId": f"artifact_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
|
|
436
396
|
"name": name,
|
|
437
397
|
"parts": parts,
|
|
438
|
-
"timestamp": datetime.now().isoformat()
|
|
398
|
+
"timestamp": datetime.now().isoformat(),
|
|
439
399
|
}
|
|
440
400
|
|
|
441
401
|
updated_task = task.copy()
|
|
@@ -452,7 +412,7 @@ def complete_a2a_task(task: Dict[str, Any], result: Optional[Any] = None) -> Dic
|
|
|
452
412
|
"artifactId": f"result_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
|
|
453
413
|
"name": "final_result",
|
|
454
414
|
"parts": [{"kind": "text", "text": str(result)}],
|
|
455
|
-
"timestamp": datetime.now().isoformat()
|
|
415
|
+
"timestamp": datetime.now().isoformat(),
|
|
456
416
|
}
|
|
457
417
|
|
|
458
418
|
updated_task["artifacts"] = [*task.get("artifacts", []), result_artifact]
|
|
@@ -462,10 +422,9 @@ def complete_a2a_task(task: Dict[str, Any], result: Optional[Any] = None) -> Dic
|
|
|
462
422
|
|
|
463
423
|
# Execution functions
|
|
464
424
|
|
|
425
|
+
|
|
465
426
|
async def execute_a2a_agent(
|
|
466
|
-
context: Dict[str, Any],
|
|
467
|
-
agent: A2AAgent,
|
|
468
|
-
model_provider: Any
|
|
427
|
+
context: Dict[str, Any], agent: A2AAgent, model_provider: Any
|
|
469
428
|
) -> Dict[str, Any]:
|
|
470
429
|
"""Execute A2A agent and return result"""
|
|
471
430
|
message = context.get("message", {})
|
|
@@ -495,40 +454,36 @@ async def execute_a2a_agent(
|
|
|
495
454
|
updated_task = update_a2a_task_status(
|
|
496
455
|
current_task,
|
|
497
456
|
"failed",
|
|
498
|
-
create_a2a_text_message(error_message, session_id, current_task["id"])
|
|
457
|
+
create_a2a_text_message(error_message, session_id, current_task["id"]),
|
|
499
458
|
)
|
|
500
459
|
|
|
501
460
|
return {
|
|
502
461
|
"events": [*events, *processing_events],
|
|
503
462
|
"final_task": updated_task,
|
|
504
|
-
"error": error_message
|
|
463
|
+
"error": error_message,
|
|
505
464
|
}
|
|
506
465
|
else:
|
|
507
466
|
# Success case
|
|
508
467
|
content = event.content
|
|
509
468
|
updated_task = add_artifact_to_a2a_task(
|
|
510
|
-
current_task,
|
|
511
|
-
[{"kind": "text", "text": str(content)}],
|
|
512
|
-
"response"
|
|
469
|
+
current_task, [{"kind": "text", "text": str(content)}], "response"
|
|
513
470
|
)
|
|
514
471
|
updated_task = complete_a2a_task(updated_task, content)
|
|
515
472
|
|
|
516
|
-
return {
|
|
517
|
-
"events": [*events, *processing_events],
|
|
518
|
-
"final_task": updated_task
|
|
519
|
-
}
|
|
473
|
+
return {"events": [*events, *processing_events], "final_task": updated_task}
|
|
520
474
|
|
|
521
475
|
# If we reach here without completion, mark as failed
|
|
522
476
|
updated_task = update_a2a_task_status(current_task, "failed")
|
|
523
477
|
return {
|
|
524
478
|
"events": [*events, *processing_events],
|
|
525
479
|
"final_task": updated_task,
|
|
526
|
-
"error": "Agent processing did not complete"
|
|
480
|
+
"error": "Agent processing did not complete",
|
|
527
481
|
}
|
|
528
482
|
|
|
529
483
|
except Exception as error:
|
|
530
484
|
# Log the actual error for debugging but don't expose internal details
|
|
531
485
|
import logging
|
|
486
|
+
|
|
532
487
|
logging.error(f"Agent execution error for {agent.name}: {error!s}", exc_info=True)
|
|
533
488
|
|
|
534
489
|
# Determine error type and provide safe error message
|
|
@@ -546,20 +501,14 @@ async def execute_a2a_agent(
|
|
|
546
501
|
failed_task = update_a2a_task_status(
|
|
547
502
|
current_task,
|
|
548
503
|
"failed",
|
|
549
|
-
create_a2a_text_message(safe_error, session_id, current_task["id"])
|
|
504
|
+
create_a2a_text_message(safe_error, session_id, current_task["id"]),
|
|
550
505
|
)
|
|
551
506
|
|
|
552
|
-
return {
|
|
553
|
-
"events": events,
|
|
554
|
-
"final_task": failed_task,
|
|
555
|
-
"error": safe_error
|
|
556
|
-
}
|
|
507
|
+
return {"events": events, "final_task": failed_task, "error": safe_error}
|
|
557
508
|
|
|
558
509
|
|
|
559
510
|
async def execute_a2a_agent_with_streaming(
|
|
560
|
-
context: Dict[str, Any],
|
|
561
|
-
agent: A2AAgent,
|
|
562
|
-
model_provider: Any
|
|
511
|
+
context: Dict[str, Any], agent: A2AAgent, model_provider: Any
|
|
563
512
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
564
513
|
"""Execute A2A agent with streaming"""
|
|
565
514
|
message = context.get("message", {})
|
|
@@ -576,7 +525,7 @@ async def execute_a2a_agent_with_streaming(
|
|
|
576
525
|
"taskId": current_task["id"],
|
|
577
526
|
"contextId": current_task["contextId"],
|
|
578
527
|
"status": {"state": "submitted", "timestamp": datetime.now().isoformat()},
|
|
579
|
-
"final": False
|
|
528
|
+
"final": False,
|
|
580
529
|
}
|
|
581
530
|
|
|
582
531
|
yield {
|
|
@@ -584,7 +533,7 @@ async def execute_a2a_agent_with_streaming(
|
|
|
584
533
|
"taskId": current_task["id"],
|
|
585
534
|
"contextId": current_task["contextId"],
|
|
586
535
|
"status": {"state": "working", "timestamp": datetime.now().isoformat()},
|
|
587
|
-
"final": False
|
|
536
|
+
"final": False,
|
|
588
537
|
}
|
|
589
538
|
|
|
590
539
|
try:
|
|
@@ -601,11 +550,11 @@ async def execute_a2a_agent_with_streaming(
|
|
|
601
550
|
"message": create_a2a_text_message(
|
|
602
551
|
event.updates or "Processing...",
|
|
603
552
|
current_task["contextId"],
|
|
604
|
-
current_task["id"]
|
|
553
|
+
current_task["id"],
|
|
605
554
|
),
|
|
606
|
-
"timestamp": event.timestamp
|
|
555
|
+
"timestamp": event.timestamp,
|
|
607
556
|
},
|
|
608
|
-
"final": False
|
|
557
|
+
"final": False,
|
|
609
558
|
}
|
|
610
559
|
else:
|
|
611
560
|
# Handle final result
|
|
@@ -617,10 +566,12 @@ async def execute_a2a_agent_with_streaming(
|
|
|
617
566
|
"contextId": current_task["contextId"],
|
|
618
567
|
"status": {
|
|
619
568
|
"state": "failed",
|
|
620
|
-
"message": create_a2a_text_message(
|
|
621
|
-
|
|
569
|
+
"message": create_a2a_text_message(
|
|
570
|
+
error_message, current_task["contextId"], current_task["id"]
|
|
571
|
+
),
|
|
572
|
+
"timestamp": event.timestamp,
|
|
622
573
|
},
|
|
623
|
-
"final": True
|
|
574
|
+
"final": True,
|
|
624
575
|
}
|
|
625
576
|
else:
|
|
626
577
|
yield {
|
|
@@ -630,8 +581,8 @@ async def execute_a2a_agent_with_streaming(
|
|
|
630
581
|
"artifact": {
|
|
631
582
|
"artifactId": f"result_{int(time.time() * 1000)}",
|
|
632
583
|
"name": "response",
|
|
633
|
-
"parts": [{"kind": "text", "text": str(event.content)}]
|
|
634
|
-
}
|
|
584
|
+
"parts": [{"kind": "text", "text": str(event.content)}],
|
|
585
|
+
},
|
|
635
586
|
}
|
|
636
587
|
|
|
637
588
|
yield {
|
|
@@ -639,7 +590,7 @@ async def execute_a2a_agent_with_streaming(
|
|
|
639
590
|
"taskId": current_task["id"],
|
|
640
591
|
"contextId": current_task["contextId"],
|
|
641
592
|
"status": {"state": "completed", "timestamp": event.timestamp},
|
|
642
|
-
"final": True
|
|
593
|
+
"final": True,
|
|
643
594
|
}
|
|
644
595
|
break
|
|
645
596
|
|
|
@@ -651,8 +602,10 @@ async def execute_a2a_agent_with_streaming(
|
|
|
651
602
|
"contextId": current_task["contextId"],
|
|
652
603
|
"status": {
|
|
653
604
|
"state": "failed",
|
|
654
|
-
"message": create_a2a_text_message(
|
|
655
|
-
|
|
605
|
+
"message": create_a2a_text_message(
|
|
606
|
+
error_message, current_task["contextId"], current_task["id"]
|
|
607
|
+
),
|
|
608
|
+
"timestamp": datetime.now().isoformat(),
|
|
656
609
|
},
|
|
657
|
-
"final": True
|
|
610
|
+
"final": True,
|
|
658
611
|
}
|