xpander-sdk 2.0.144__py3-none-any.whl → 2.0.192__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.
- xpander_sdk/__init__.py +6 -0
- xpander_sdk/consts/api_routes.py +9 -0
- xpander_sdk/models/activity.py +65 -0
- xpander_sdk/models/compactization.py +112 -0
- xpander_sdk/models/deep_planning.py +18 -0
- xpander_sdk/models/events.py +6 -0
- xpander_sdk/models/frameworks.py +2 -2
- xpander_sdk/models/generic.py +27 -0
- xpander_sdk/models/notifications.py +98 -0
- xpander_sdk/models/orchestrations.py +271 -0
- xpander_sdk/modules/agents/models/agent.py +11 -5
- xpander_sdk/modules/agents/sub_modules/agent.py +25 -10
- xpander_sdk/modules/backend/__init__.py +8 -0
- xpander_sdk/modules/backend/backend_module.py +47 -2
- xpander_sdk/modules/backend/decorators/__init__.py +7 -0
- xpander_sdk/modules/backend/decorators/on_auth_event.py +131 -0
- xpander_sdk/modules/backend/events_registry.py +172 -0
- xpander_sdk/modules/backend/frameworks/agno.py +377 -15
- xpander_sdk/modules/backend/frameworks/dispatch.py +3 -1
- xpander_sdk/modules/backend/utils/mcp_oauth.py +37 -25
- xpander_sdk/modules/events/decorators/__init__.py +3 -0
- xpander_sdk/modules/events/decorators/on_tool.py +384 -0
- xpander_sdk/modules/events/events_module.py +28 -1
- xpander_sdk/modules/tasks/models/task.py +3 -14
- xpander_sdk/modules/tasks/sub_modules/task.py +276 -84
- xpander_sdk/modules/tools_repository/models/mcp.py +1 -0
- xpander_sdk/modules/tools_repository/sub_modules/tool.py +46 -15
- xpander_sdk/modules/tools_repository/tools_repository_module.py +6 -2
- xpander_sdk/modules/tools_repository/utils/generic.py +3 -0
- xpander_sdk/utils/agents/__init__.py +0 -0
- xpander_sdk/utils/agents/compactization_agent.py +257 -0
- xpander_sdk/utils/generic.py +5 -0
- {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/METADATA +224 -14
- {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/RECORD +37 -24
- {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/WHEEL +0 -0
- {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/licenses/LICENSE +0 -0
- {xpander_sdk-2.0.144.dist-info → xpander_sdk-2.0.192.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xpander_sdk.decorators.on_tool
|
|
3
|
+
|
|
4
|
+
This module provides decorators for tool invocation lifecycle hooks:
|
|
5
|
+
- `@on_tool_before`: Execute before tool invocation
|
|
6
|
+
- `@on_tool_after`: Execute after successful tool invocation
|
|
7
|
+
- `@on_tool_error`: Execute when tool invocation fails
|
|
8
|
+
|
|
9
|
+
The decorators ensure that registered functions:
|
|
10
|
+
- Accept parameters: Tool, payload, payload_extension, tool_call_id, agent_version
|
|
11
|
+
- Can be either synchronous or asynchronous
|
|
12
|
+
- Are called at the appropriate lifecycle stage during tool execution
|
|
13
|
+
|
|
14
|
+
Execution Notes:
|
|
15
|
+
- Before-hooks execute before tool invocation and can perform validation or logging
|
|
16
|
+
- After-hooks execute after successful invocation with access to the result
|
|
17
|
+
- Error-hooks execute when an exception occurs during invocation
|
|
18
|
+
- Multiple hooks of the same type can be registered and will execute in registration order
|
|
19
|
+
- Exceptions in hooks are logged but don't prevent tool execution
|
|
20
|
+
|
|
21
|
+
Example usage:
|
|
22
|
+
--------------
|
|
23
|
+
>>> @on_tool_before
|
|
24
|
+
... async def log_tool_invocation(tool, payload, payload_extension, tool_call_id, agent_version):
|
|
25
|
+
... logger.info(f"Invoking tool {tool.name} with payload: {payload}")
|
|
26
|
+
|
|
27
|
+
>>> @on_tool_after
|
|
28
|
+
... def record_tool_result(tool, payload, payload_extension, tool_call_id, agent_version, result):
|
|
29
|
+
... logger.info(f"Tool {tool.name} completed with result: {result}")
|
|
30
|
+
|
|
31
|
+
>>> @on_tool_error
|
|
32
|
+
... async def handle_tool_error(tool, payload, payload_extension, tool_call_id, agent_version, error):
|
|
33
|
+
... logger.error(f"Tool {tool.name} failed: {error}")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import asyncio
|
|
37
|
+
from functools import wraps
|
|
38
|
+
from inspect import iscoroutinefunction
|
|
39
|
+
from typing import Optional, Callable, Any, Dict, List
|
|
40
|
+
|
|
41
|
+
from xpander_sdk.models.configuration import Configuration
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ToolHooksRegistry:
|
|
45
|
+
"""
|
|
46
|
+
Registry for tool invocation lifecycle hooks.
|
|
47
|
+
|
|
48
|
+
This class maintains class-level lists of hooks that are executed at different
|
|
49
|
+
stages of tool invocation: before, after, and on error.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
_before_hooks: List[Callable] = []
|
|
53
|
+
_after_hooks: List[Callable] = []
|
|
54
|
+
_error_hooks: List[Callable] = []
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def register_before_hook(cls, hook: Callable) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Register a before-invocation hook.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
hook (Callable): The hook function to execute before tool invocation.
|
|
63
|
+
"""
|
|
64
|
+
cls._before_hooks.append(hook)
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def register_after_hook(cls, hook: Callable) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Register an after-invocation hook.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
hook (Callable): The hook function to execute after successful tool invocation.
|
|
73
|
+
"""
|
|
74
|
+
cls._after_hooks.append(hook)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def register_error_hook(cls, hook: Callable) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Register an error hook.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
hook (Callable): The hook function to execute when tool invocation fails.
|
|
83
|
+
"""
|
|
84
|
+
cls._error_hooks.append(hook)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
async def execute_before_hooks(
|
|
88
|
+
cls,
|
|
89
|
+
tool: Any,
|
|
90
|
+
payload: Any,
|
|
91
|
+
payload_extension: Optional[Dict[str, Any]] = None,
|
|
92
|
+
tool_call_id: Optional[str] = None,
|
|
93
|
+
agent_version: Optional[str] = None
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Execute all registered before-invocation hooks.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
tool: The Tool object being invoked.
|
|
100
|
+
payload: The payload being sent to the tool.
|
|
101
|
+
payload_extension: Additional payload data.
|
|
102
|
+
tool_call_id: Unique ID of the tool call.
|
|
103
|
+
agent_version: Version of the agent making the call.
|
|
104
|
+
"""
|
|
105
|
+
from loguru import logger
|
|
106
|
+
|
|
107
|
+
for hook in cls._before_hooks:
|
|
108
|
+
try:
|
|
109
|
+
if asyncio.iscoroutinefunction(hook):
|
|
110
|
+
await hook(tool, payload, payload_extension, tool_call_id, agent_version)
|
|
111
|
+
else:
|
|
112
|
+
hook(tool, payload, payload_extension, tool_call_id, agent_version)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"Before-hook {hook.__name__} failed: {e}")
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
async def execute_after_hooks(
|
|
118
|
+
cls,
|
|
119
|
+
tool: Any,
|
|
120
|
+
payload: Any,
|
|
121
|
+
payload_extension: Optional[Dict[str, Any]] = None,
|
|
122
|
+
tool_call_id: Optional[str] = None,
|
|
123
|
+
agent_version: Optional[str] = None,
|
|
124
|
+
result: Any = None
|
|
125
|
+
) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Execute all registered after-invocation hooks.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
tool: The Tool object that was invoked.
|
|
131
|
+
payload: The payload sent to the tool.
|
|
132
|
+
payload_extension: Additional payload data.
|
|
133
|
+
tool_call_id: Unique ID of the tool call.
|
|
134
|
+
agent_version: Version of the agent that made the call.
|
|
135
|
+
result: The result returned by the tool invocation.
|
|
136
|
+
"""
|
|
137
|
+
from loguru import logger
|
|
138
|
+
|
|
139
|
+
for hook in cls._after_hooks:
|
|
140
|
+
try:
|
|
141
|
+
if asyncio.iscoroutinefunction(hook):
|
|
142
|
+
await hook(tool, payload, payload_extension, tool_call_id, agent_version, result)
|
|
143
|
+
else:
|
|
144
|
+
hook(tool, payload, payload_extension, tool_call_id, agent_version, result)
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.error(f"After-hook {hook.__name__} failed: {e}")
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
async def execute_error_hooks(
|
|
150
|
+
cls,
|
|
151
|
+
tool: Any,
|
|
152
|
+
payload: Any,
|
|
153
|
+
payload_extension: Optional[Dict[str, Any]] = None,
|
|
154
|
+
tool_call_id: Optional[str] = None,
|
|
155
|
+
agent_version: Optional[str] = None,
|
|
156
|
+
error: Optional[Exception] = None
|
|
157
|
+
) -> None:
|
|
158
|
+
"""
|
|
159
|
+
Execute all registered error hooks.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
tool: The Tool object that failed.
|
|
163
|
+
payload: The payload sent to the tool.
|
|
164
|
+
payload_extension: Additional payload data.
|
|
165
|
+
tool_call_id: Unique ID of the tool call.
|
|
166
|
+
agent_version: Version of the agent that made the call.
|
|
167
|
+
error: The exception that occurred during invocation.
|
|
168
|
+
"""
|
|
169
|
+
from loguru import logger
|
|
170
|
+
|
|
171
|
+
for hook in cls._error_hooks:
|
|
172
|
+
try:
|
|
173
|
+
if asyncio.iscoroutinefunction(hook):
|
|
174
|
+
await hook(tool, payload, payload_extension, tool_call_id, agent_version, error)
|
|
175
|
+
else:
|
|
176
|
+
hook(tool, payload, payload_extension, tool_call_id, agent_version, error)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"Error-hook {hook.__name__} failed: {e}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def on_tool_before(
|
|
182
|
+
_func: Optional[Callable] = None,
|
|
183
|
+
*,
|
|
184
|
+
configuration: Optional[Configuration] = None
|
|
185
|
+
):
|
|
186
|
+
"""
|
|
187
|
+
Decorator to register a handler as a before-invocation hook for tool calls.
|
|
188
|
+
|
|
189
|
+
The decorated function will be executed before any tool invocation. The function:
|
|
190
|
+
- Must accept parameters: tool, payload, payload_extension, tool_call_id, agent_version
|
|
191
|
+
- Can be either synchronous or asynchronous
|
|
192
|
+
- Can perform validation, logging, or modification of invocation context
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
_func (Optional[Callable]):
|
|
196
|
+
The function to decorate (for direct usage like `@on_tool_before`).
|
|
197
|
+
configuration (Optional[Configuration]):
|
|
198
|
+
An optional configuration object (reserved for future use).
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
>>> from typing import Optional, Dict, Any
|
|
202
|
+
>>> from xpander_sdk import Tool
|
|
203
|
+
>>>
|
|
204
|
+
>>> @on_tool_before
|
|
205
|
+
... async def validate_tool_input(
|
|
206
|
+
... tool: Tool,
|
|
207
|
+
... payload: Any,
|
|
208
|
+
... payload_extension: Optional[Dict[str, Any]] = None,
|
|
209
|
+
... tool_call_id: Optional[str] = None,
|
|
210
|
+
... agent_version: Optional[str] = None
|
|
211
|
+
... ):
|
|
212
|
+
... logger.info(f"Pre-invoking tool: {tool.name}")
|
|
213
|
+
... if not payload:
|
|
214
|
+
... logger.warning("Empty payload provided")
|
|
215
|
+
|
|
216
|
+
>>> @on_tool_before
|
|
217
|
+
... def log_tool_metrics(
|
|
218
|
+
... tool: Tool,
|
|
219
|
+
... payload: Any,
|
|
220
|
+
... payload_extension: Optional[Dict[str, Any]] = None,
|
|
221
|
+
... tool_call_id: Optional[str] = None,
|
|
222
|
+
... agent_version: Optional[str] = None
|
|
223
|
+
... ):
|
|
224
|
+
... metrics.record_tool_invocation(tool.name, tool_call_id)
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def decorator(func: Callable) -> Callable:
|
|
228
|
+
@wraps(func)
|
|
229
|
+
async def async_wrapper(*args, **kwargs):
|
|
230
|
+
return await func(*args, **kwargs)
|
|
231
|
+
|
|
232
|
+
@wraps(func)
|
|
233
|
+
def sync_wrapper(*args, **kwargs):
|
|
234
|
+
return func(*args, **kwargs)
|
|
235
|
+
|
|
236
|
+
wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
|
|
237
|
+
|
|
238
|
+
# Register hook in the registry
|
|
239
|
+
ToolHooksRegistry.register_before_hook(wrapped)
|
|
240
|
+
|
|
241
|
+
return wrapped
|
|
242
|
+
|
|
243
|
+
if _func and callable(_func):
|
|
244
|
+
return decorator(_func)
|
|
245
|
+
|
|
246
|
+
return decorator
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def on_tool_after(
|
|
250
|
+
_func: Optional[Callable] = None,
|
|
251
|
+
*,
|
|
252
|
+
configuration: Optional[Configuration] = None
|
|
253
|
+
):
|
|
254
|
+
"""
|
|
255
|
+
Decorator to register a handler as an after-invocation hook for tool calls.
|
|
256
|
+
|
|
257
|
+
The decorated function will be executed after successful tool invocation. The function:
|
|
258
|
+
- Must accept parameters: tool, payload, payload_extension, tool_call_id, agent_version, result
|
|
259
|
+
- Can be either synchronous or asynchronous
|
|
260
|
+
- Can perform logging, analytics, or result processing
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
_func (Optional[Callable]):
|
|
264
|
+
The function to decorate (for direct usage like `@on_tool_after`).
|
|
265
|
+
configuration (Optional[Configuration]):
|
|
266
|
+
An optional configuration object (reserved for future use).
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
>>> from typing import Optional, Dict, Any
|
|
270
|
+
>>> from xpander_sdk import Tool
|
|
271
|
+
>>>
|
|
272
|
+
>>> @on_tool_after
|
|
273
|
+
... async def log_tool_success(
|
|
274
|
+
... tool: Tool,
|
|
275
|
+
... payload: Any,
|
|
276
|
+
... payload_extension: Optional[Dict[str, Any]] = None,
|
|
277
|
+
... tool_call_id: Optional[str] = None,
|
|
278
|
+
... agent_version: Optional[str] = None,
|
|
279
|
+
... result: Any = None
|
|
280
|
+
... ):
|
|
281
|
+
... logger.info(f"Tool {tool.name} succeeded with result: {result}")
|
|
282
|
+
... analytics.record_success(tool.name, tool_call_id)
|
|
283
|
+
|
|
284
|
+
>>> @on_tool_after
|
|
285
|
+
... def cache_tool_result(
|
|
286
|
+
... tool: Tool,
|
|
287
|
+
... payload: Any,
|
|
288
|
+
... payload_extension: Optional[Dict[str, Any]] = None,
|
|
289
|
+
... tool_call_id: Optional[str] = None,
|
|
290
|
+
... agent_version: Optional[str] = None,
|
|
291
|
+
... result: Any = None
|
|
292
|
+
... ):
|
|
293
|
+
... cache.set(f"tool_{tool.id}_{hash(payload)}", result)
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def decorator(func: Callable) -> Callable:
|
|
297
|
+
@wraps(func)
|
|
298
|
+
async def async_wrapper(*args, **kwargs):
|
|
299
|
+
return await func(*args, **kwargs)
|
|
300
|
+
|
|
301
|
+
@wraps(func)
|
|
302
|
+
def sync_wrapper(*args, **kwargs):
|
|
303
|
+
return func(*args, **kwargs)
|
|
304
|
+
|
|
305
|
+
wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
|
|
306
|
+
|
|
307
|
+
# Register hook in the registry
|
|
308
|
+
ToolHooksRegistry.register_after_hook(wrapped)
|
|
309
|
+
|
|
310
|
+
return wrapped
|
|
311
|
+
|
|
312
|
+
if _func and callable(_func):
|
|
313
|
+
return decorator(_func)
|
|
314
|
+
|
|
315
|
+
return decorator
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def on_tool_error(
|
|
319
|
+
_func: Optional[Callable] = None,
|
|
320
|
+
*,
|
|
321
|
+
configuration: Optional[Configuration] = None
|
|
322
|
+
):
|
|
323
|
+
"""
|
|
324
|
+
Decorator to register a handler as an error hook for tool calls.
|
|
325
|
+
|
|
326
|
+
The decorated function will be executed when tool invocation fails. The function:
|
|
327
|
+
- Must accept parameters: tool, payload, payload_extension, tool_call_id, agent_version, error
|
|
328
|
+
- Can be either synchronous or asynchronous
|
|
329
|
+
- Can perform error logging, alerting, or recovery actions
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
_func (Optional[Callable]):
|
|
333
|
+
The function to decorate (for direct usage like `@on_tool_error`).
|
|
334
|
+
configuration (Optional[Configuration]):
|
|
335
|
+
An optional configuration object (reserved for future use).
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> from typing import Optional, Dict, Any
|
|
339
|
+
>>> from xpander_sdk import Tool
|
|
340
|
+
>>>
|
|
341
|
+
>>> @on_tool_error
|
|
342
|
+
... async def alert_on_tool_failure(
|
|
343
|
+
... tool: Tool,
|
|
344
|
+
... payload: Any,
|
|
345
|
+
... payload_extension: Optional[Dict[str, Any]] = None,
|
|
346
|
+
... tool_call_id: Optional[str] = None,
|
|
347
|
+
... agent_version: Optional[str] = None,
|
|
348
|
+
... error: Optional[Exception] = None
|
|
349
|
+
... ):
|
|
350
|
+
... logger.error(f"Tool {tool.name} failed: {error}")
|
|
351
|
+
... await send_alert(f"Tool failure: {tool.name}", str(error))
|
|
352
|
+
|
|
353
|
+
>>> @on_tool_error
|
|
354
|
+
... def log_tool_failure(
|
|
355
|
+
... tool: Tool,
|
|
356
|
+
... payload: Any,
|
|
357
|
+
... payload_extension: Optional[Dict[str, Any]] = None,
|
|
358
|
+
... tool_call_id: Optional[str] = None,
|
|
359
|
+
... agent_version: Optional[str] = None,
|
|
360
|
+
... error: Optional[Exception] = None
|
|
361
|
+
... ):
|
|
362
|
+
... error_log.write(f"{tool.name},{tool_call_id},{str(error)}\n")
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
def decorator(func: Callable) -> Callable:
|
|
366
|
+
@wraps(func)
|
|
367
|
+
async def async_wrapper(*args, **kwargs):
|
|
368
|
+
return await func(*args, **kwargs)
|
|
369
|
+
|
|
370
|
+
@wraps(func)
|
|
371
|
+
def sync_wrapper(*args, **kwargs):
|
|
372
|
+
return func(*args, **kwargs)
|
|
373
|
+
|
|
374
|
+
wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
|
|
375
|
+
|
|
376
|
+
# Register hook in the registry
|
|
377
|
+
ToolHooksRegistry.register_error_hook(wrapped)
|
|
378
|
+
|
|
379
|
+
return wrapped
|
|
380
|
+
|
|
381
|
+
if _func and callable(_func):
|
|
382
|
+
return decorator(_func)
|
|
383
|
+
|
|
384
|
+
return decorator
|
|
@@ -81,6 +81,7 @@ class Events(ModuleBase):
|
|
|
81
81
|
|
|
82
82
|
worker: Optional[DeployedAsset] = None
|
|
83
83
|
test_task: Optional[LocalTaskTest] = None
|
|
84
|
+
_heartbeat_task: Optional[asyncio.Task] = None
|
|
84
85
|
|
|
85
86
|
# Class-level registries for boot and shutdown handlers
|
|
86
87
|
_boot_handlers: List[BootHandler] = []
|
|
@@ -330,6 +331,7 @@ class Events(ModuleBase):
|
|
|
330
331
|
agent_worker: DeployedAsset,
|
|
331
332
|
task: Task,
|
|
332
333
|
on_execution_request: ExecutionRequestHandler,
|
|
334
|
+
retry_count: Optional[int] = 0,
|
|
333
335
|
) -> None:
|
|
334
336
|
"""
|
|
335
337
|
Handle an incoming task execution request.
|
|
@@ -338,6 +340,7 @@ class Events(ModuleBase):
|
|
|
338
340
|
agent_worker (DeployedAsset): The deployed asset (agent) to handle the task.
|
|
339
341
|
task (Task): The task object containing execution details.
|
|
340
342
|
on_execution_request (ExecutionRequestHandler): The handler function to process the task.
|
|
343
|
+
retry_count (Optional[int]): Current retry attempt count. Defaults to 0.
|
|
341
344
|
"""
|
|
342
345
|
error = None
|
|
343
346
|
try:
|
|
@@ -351,6 +354,25 @@ class Events(ModuleBase):
|
|
|
351
354
|
on_execution_request,
|
|
352
355
|
task,
|
|
353
356
|
)
|
|
357
|
+
|
|
358
|
+
# Check if plan is complete, retry if not
|
|
359
|
+
plan_following_status = await task.aget_plan_following_status()
|
|
360
|
+
if not plan_following_status.can_finish:
|
|
361
|
+
# Check if we've exceeded max retries
|
|
362
|
+
if retry_count >= 50: # 0, 1, 2 = 50 total attempts
|
|
363
|
+
logger.warning(f"Failed to complete plan after {retry_count + 1} attempts. Remaining incomplete tasks.")
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
# Recursively call with incremented retry count
|
|
367
|
+
logger.info(f"Plan not complete, retrying (attempt {retry_count + 2})")
|
|
368
|
+
await self.handle_task_execution_request(
|
|
369
|
+
agent_worker,
|
|
370
|
+
task,
|
|
371
|
+
on_execution_request,
|
|
372
|
+
retry_count=retry_count + 1
|
|
373
|
+
)
|
|
374
|
+
return
|
|
375
|
+
|
|
354
376
|
except Exception as e:
|
|
355
377
|
logger.exception(f"Execution handler failed - {str(e)}")
|
|
356
378
|
error = str(e)
|
|
@@ -479,7 +501,12 @@ class Events(ModuleBase):
|
|
|
479
501
|
)
|
|
480
502
|
)
|
|
481
503
|
|
|
482
|
-
|
|
504
|
+
# Cancel previous heartbeat task if it exists and start a new one
|
|
505
|
+
if self._heartbeat_task and not self._heartbeat_task.done():
|
|
506
|
+
logger.debug(f"Canceling previous heartbeat task for worker {agent_worker.id}")
|
|
507
|
+
self._heartbeat_task.cancel()
|
|
508
|
+
self._heartbeat_task = asyncio.create_task(self.heartbeat_loop(agent_worker.id))
|
|
509
|
+
self.track(self._heartbeat_task)
|
|
483
510
|
|
|
484
511
|
elif event.event == EventType.AgentExecution:
|
|
485
512
|
task = Task(**json.loads(event.data), configuration=self.configuration)
|
|
@@ -39,26 +39,15 @@ class AgentExecutionStatus(str, Enum):
|
|
|
39
39
|
Stopped = "stopped"
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
class
|
|
42
|
+
class HumanInTheLoopRequest(BaseModel):
|
|
43
43
|
"""
|
|
44
44
|
Model representing human-in-the-loop approval records for tasks.
|
|
45
45
|
|
|
46
46
|
Attributes:
|
|
47
|
-
|
|
48
|
-
approved_by (Optional[str]): User who approved the operation.
|
|
49
|
-
rejected_by (Optional[str]): User who rejected the operation.
|
|
50
|
-
title (Optional[str]): Title/subject of the approval request.
|
|
51
|
-
description (Optional[str]): Detailed description of the approval.
|
|
52
|
-
content (str): Content or action that requires approval.
|
|
47
|
+
wait_node_id (str): The id of the node that triggered this HITL.
|
|
53
48
|
"""
|
|
54
49
|
|
|
55
|
-
|
|
56
|
-
approved_by: Optional[str] = None
|
|
57
|
-
rejected_by: Optional[str] = None
|
|
58
|
-
title: Optional[str] = None
|
|
59
|
-
description: Optional[str] = None
|
|
60
|
-
content: str
|
|
61
|
-
|
|
50
|
+
wait_node_id: str
|
|
62
51
|
|
|
63
52
|
class AgentExecutionInput(BaseModel):
|
|
64
53
|
"""
|