xpander-sdk 2.0.143__py3-none-any.whl → 2.0.161__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/consts/api_routes.py +1 -0
- xpander_sdk/models/activity.py +65 -0
- xpander_sdk/models/deep_planning.py +18 -0
- xpander_sdk/models/events.py +3 -0
- xpander_sdk/modules/agents/models/agent.py +4 -1
- xpander_sdk/modules/agents/sub_modules/agent.py +7 -0
- xpander_sdk/modules/backend/frameworks/agno.py +245 -5
- xpander_sdk/modules/backend/utils/mcp_oauth.py +1 -1
- xpander_sdk/modules/events/events_module.py +34 -0
- xpander_sdk/modules/tasks/sub_modules/task.py +235 -77
- xpander_sdk/modules/tools_repository/models/mcp.py +1 -0
- xpander_sdk/modules/tools_repository/tools_repository_module.py +6 -2
- {xpander_sdk-2.0.143.dist-info → xpander_sdk-2.0.161.dist-info}/METADATA +130 -4
- {xpander_sdk-2.0.143.dist-info → xpander_sdk-2.0.161.dist-info}/RECORD +17 -15
- {xpander_sdk-2.0.143.dist-info → xpander_sdk-2.0.161.dist-info}/WHEEL +0 -0
- {xpander_sdk-2.0.143.dist-info → xpander_sdk-2.0.161.dist-info}/licenses/LICENSE +0 -0
- {xpander_sdk-2.0.143.dist-info → xpander_sdk-2.0.161.dist-info}/top_level.txt +0 -0
|
@@ -28,22 +28,41 @@ Typical usage example:
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
from datetime import datetime
|
|
31
|
-
from typing import
|
|
31
|
+
from typing import (
|
|
32
|
+
Any,
|
|
33
|
+
AsyncGenerator,
|
|
34
|
+
Dict,
|
|
35
|
+
Generator,
|
|
36
|
+
List,
|
|
37
|
+
Optional,
|
|
38
|
+
Type,
|
|
39
|
+
TypeVar,
|
|
40
|
+
Union,
|
|
41
|
+
)
|
|
32
42
|
from httpx import HTTPStatusError
|
|
33
43
|
import httpx
|
|
34
44
|
import json
|
|
35
45
|
from httpx_sse import aconnect_sse
|
|
46
|
+
from pydantic import Field, computed_field
|
|
36
47
|
|
|
37
48
|
from xpander_sdk.consts.api_routes import APIRoute
|
|
38
49
|
from xpander_sdk.core.xpander_api_client import APIClient
|
|
39
50
|
from xpander_sdk.exceptions.module_exception import ModuleException
|
|
51
|
+
from xpander_sdk.models.activity import AgentActivityThread
|
|
40
52
|
from xpander_sdk.models.configuration import Configuration
|
|
53
|
+
from xpander_sdk.models.deep_planning import PlanFollowingStatus, DeepPlanning
|
|
41
54
|
from xpander_sdk.models.events import (
|
|
42
55
|
TaskUpdateEventType,
|
|
43
56
|
ToolCallRequest,
|
|
44
57
|
ToolCallResult,
|
|
45
58
|
)
|
|
46
|
-
from xpander_sdk.models.shared import
|
|
59
|
+
from xpander_sdk.models.shared import (
|
|
60
|
+
ExecutionTokens,
|
|
61
|
+
OutputFormat,
|
|
62
|
+
ThinkMode,
|
|
63
|
+
Tokens,
|
|
64
|
+
XPanderSharedModel,
|
|
65
|
+
)
|
|
47
66
|
from xpander_sdk.modules.events.utils.generic import get_events_base, get_events_headers
|
|
48
67
|
from xpander_sdk.modules.tasks.models.task import (
|
|
49
68
|
AgentExecutionInput,
|
|
@@ -51,16 +70,25 @@ from xpander_sdk.modules.tasks.models.task import (
|
|
|
51
70
|
HumanInTheLoop,
|
|
52
71
|
ExecutionMetricsReport,
|
|
53
72
|
PendingECARequest,
|
|
54
|
-
TaskReportRequest
|
|
73
|
+
TaskReportRequest,
|
|
74
|
+
)
|
|
75
|
+
from xpander_sdk.modules.tasks.utils.files import (
|
|
76
|
+
categorize_files,
|
|
77
|
+
fetch_urls,
|
|
78
|
+
fetch_file,
|
|
79
|
+
)
|
|
80
|
+
from xpander_sdk.modules.tools_repository.models.mcp import (
|
|
81
|
+
MCPOAuthGetTokenResponse,
|
|
82
|
+
MCPServerDetails,
|
|
55
83
|
)
|
|
56
|
-
from xpander_sdk.modules.tasks.utils.files import categorize_files, fetch_urls, fetch_file
|
|
57
|
-
from xpander_sdk.modules.tools_repository.models.mcp import MCPOAuthGetTokenResponse, MCPServerDetails
|
|
58
84
|
from xpander_sdk.utils.event_loop import run_sync
|
|
59
85
|
|
|
60
86
|
# Type variable for Task class methods
|
|
61
87
|
T = TypeVar("T", bound="Task")
|
|
62
88
|
|
|
63
|
-
TaskUpdateEventData = Union[
|
|
89
|
+
TaskUpdateEventData = Union[
|
|
90
|
+
T, ToolCallRequest, ToolCallResult, MCPOAuthGetTokenResponse, DeepPlanning
|
|
91
|
+
]
|
|
64
92
|
|
|
65
93
|
|
|
66
94
|
class TaskUpdateEvent(XPanderSharedModel):
|
|
@@ -107,12 +135,13 @@ class Task(XPanderSharedModel):
|
|
|
107
135
|
mcp_servers (Optional[List[MCPServerDetails]]): Optional list of mcp servers to use.
|
|
108
136
|
triggering_agent_id (Optional[str]): Optional triggering agent id.
|
|
109
137
|
title (Optional[str]): Optional task title.
|
|
138
|
+
deep_planning: Optional[DeepPlanning] = Field(default_factory=DeepPlanning)
|
|
110
139
|
|
|
111
140
|
Example:
|
|
112
141
|
>>> task = Task.load(task_id="task_123")
|
|
113
142
|
>>> task.set_status(AgentExecutionStatus.Running)
|
|
114
143
|
>>> task.save()
|
|
115
|
-
>>>
|
|
144
|
+
>>>
|
|
116
145
|
>>> # Get files for Agno integration
|
|
117
146
|
>>> files = task.get_files() # PDF files as Agno File objects
|
|
118
147
|
>>> images = task.get_images() # Image files as Agno Image objects
|
|
@@ -148,13 +177,14 @@ class Task(XPanderSharedModel):
|
|
|
148
177
|
output_schema: Optional[Dict] = None
|
|
149
178
|
events_streaming: Optional[bool] = False
|
|
150
179
|
additional_context: Optional[str] = None
|
|
151
|
-
expected_output: Optional[str] = None,
|
|
152
|
-
mcp_servers: Optional[List[MCPServerDetails]] = [],
|
|
153
|
-
triggering_agent_id: Optional[str] = None,
|
|
154
|
-
title: Optional[str] = None,
|
|
180
|
+
expected_output: Optional[str] = (None,)
|
|
181
|
+
mcp_servers: Optional[List[MCPServerDetails]] = ([],)
|
|
182
|
+
triggering_agent_id: Optional[str] = (None,)
|
|
183
|
+
title: Optional[str] = (None,)
|
|
155
184
|
think_mode: Optional[ThinkMode] = ThinkMode.Default
|
|
156
185
|
disable_attachment_injection: Optional[bool] = False
|
|
157
|
-
|
|
186
|
+
deep_planning: Optional[DeepPlanning] = Field(default_factory=DeepPlanning)
|
|
187
|
+
|
|
158
188
|
# metrics
|
|
159
189
|
tokens: Optional[Tokens] = None
|
|
160
190
|
used_tools: Optional[List[str]] = []
|
|
@@ -202,7 +232,6 @@ class Task(XPanderSharedModel):
|
|
|
202
232
|
self.__dict__.update(new_obj.__dict__)
|
|
203
233
|
return self
|
|
204
234
|
|
|
205
|
-
|
|
206
235
|
def reload(self):
|
|
207
236
|
"""
|
|
208
237
|
Reload the current object synchronously.
|
|
@@ -217,7 +246,6 @@ class Task(XPanderSharedModel):
|
|
|
217
246
|
"""
|
|
218
247
|
run_sync(self.areload())
|
|
219
248
|
|
|
220
|
-
|
|
221
249
|
@classmethod
|
|
222
250
|
async def aload(
|
|
223
251
|
cls: Type[T], task_id: str, configuration: Optional[Configuration] = None
|
|
@@ -243,7 +271,9 @@ class Task(XPanderSharedModel):
|
|
|
243
271
|
response_data = await client.make_request(
|
|
244
272
|
path=APIRoute.GetTask.format(task_id=task_id)
|
|
245
273
|
)
|
|
246
|
-
task = cls.model_validate(
|
|
274
|
+
task = cls.model_validate(
|
|
275
|
+
{**response_data, "configuration": configuration or Configuration()}
|
|
276
|
+
)
|
|
247
277
|
return task
|
|
248
278
|
except HTTPStatusError as e:
|
|
249
279
|
raise ModuleException(
|
|
@@ -322,7 +352,7 @@ class Task(XPanderSharedModel):
|
|
|
322
352
|
response = await client.make_request(
|
|
323
353
|
path=APIRoute.UpdateTask.format(task_id=self.id),
|
|
324
354
|
method="PATCH",
|
|
325
|
-
payload=self.model_dump_safe(),
|
|
355
|
+
payload=self.model_dump_safe(exclude={"configuration","deep_planning"}),
|
|
326
356
|
)
|
|
327
357
|
updated_task = Task(**response, configuration=self.configuration)
|
|
328
358
|
for field, value in updated_task.__dict__.items():
|
|
@@ -382,15 +412,15 @@ class Task(XPanderSharedModel):
|
|
|
382
412
|
def get_files(self) -> list[Any]:
|
|
383
413
|
"""
|
|
384
414
|
Get PDF files from task input, formatted for Agno integration.
|
|
385
|
-
|
|
415
|
+
|
|
386
416
|
Returns PDF files as Agno File objects when the Agno framework is available,
|
|
387
417
|
or as URL strings otherwise. This method is designed for seamless integration
|
|
388
418
|
with Agno agents.
|
|
389
|
-
|
|
419
|
+
|
|
390
420
|
Returns:
|
|
391
421
|
list[Any]: List of File objects (when Agno is available) or URL strings.
|
|
392
422
|
Returns empty list if no PDF files are present in task input.
|
|
393
|
-
|
|
423
|
+
|
|
394
424
|
Example:
|
|
395
425
|
>>> files = task.get_files()
|
|
396
426
|
>>> result = await agno_agent.arun(
|
|
@@ -398,33 +428,34 @@ class Task(XPanderSharedModel):
|
|
|
398
428
|
... files=files
|
|
399
429
|
... )
|
|
400
430
|
"""
|
|
401
|
-
|
|
431
|
+
|
|
402
432
|
if not self.input.files or len(self.input.files) == 0:
|
|
403
433
|
return []
|
|
404
|
-
|
|
434
|
+
|
|
405
435
|
categorized_files = categorize_files(file_urls=self.input.files)
|
|
406
|
-
|
|
436
|
+
|
|
407
437
|
if not categorized_files.pdfs or len(categorized_files.pdfs) == 0:
|
|
408
438
|
return []
|
|
409
439
|
|
|
410
440
|
try:
|
|
411
|
-
from agno.media import File
|
|
441
|
+
from agno.media import File # test import
|
|
442
|
+
|
|
412
443
|
return [fetch_file(url=url) for url in categorized_files.pdfs]
|
|
413
444
|
except Exception:
|
|
414
445
|
return categorized_files.pdfs
|
|
415
|
-
|
|
446
|
+
|
|
416
447
|
def get_images(self) -> list[Any]:
|
|
417
448
|
"""
|
|
418
449
|
Get image files from task input, formatted for Agno integration.
|
|
419
|
-
|
|
450
|
+
|
|
420
451
|
Returns image files as Agno Image objects when the Agno framework is available,
|
|
421
452
|
or as URL strings otherwise. This method is designed for seamless integration
|
|
422
453
|
with Agno agents that support image processing.
|
|
423
|
-
|
|
454
|
+
|
|
424
455
|
Returns:
|
|
425
456
|
list[Any]: List of Image objects (when Agno is available) or URL strings.
|
|
426
457
|
Returns empty list if no image files are present in task input.
|
|
427
|
-
|
|
458
|
+
|
|
428
459
|
Example:
|
|
429
460
|
>>> images = task.get_images()
|
|
430
461
|
>>> result = await agno_agent.arun(
|
|
@@ -434,30 +465,31 @@ class Task(XPanderSharedModel):
|
|
|
434
465
|
"""
|
|
435
466
|
if not self.input.files or len(self.input.files) == 0:
|
|
436
467
|
return []
|
|
437
|
-
|
|
468
|
+
|
|
438
469
|
categorized_files = categorize_files(file_urls=self.input.files)
|
|
439
|
-
|
|
470
|
+
|
|
440
471
|
if not categorized_files.images or len(categorized_files.images) == 0:
|
|
441
472
|
return []
|
|
442
473
|
|
|
443
474
|
try:
|
|
444
475
|
from agno.media import Image
|
|
476
|
+
|
|
445
477
|
return [Image(url=url) for url in categorized_files.images]
|
|
446
478
|
except Exception:
|
|
447
479
|
return categorized_files.images
|
|
448
|
-
|
|
480
|
+
|
|
449
481
|
def get_human_readable_files(self) -> list[Any]:
|
|
450
482
|
"""
|
|
451
483
|
Get human-readable files from task input with their content.
|
|
452
|
-
|
|
484
|
+
|
|
453
485
|
Returns text-based files (like .txt, .csv, .json, .py, etc.) with their content
|
|
454
486
|
fetched and parsed. This method is automatically used by to_message() to include
|
|
455
487
|
file contents in the task message.
|
|
456
|
-
|
|
488
|
+
|
|
457
489
|
Returns:
|
|
458
490
|
list[dict[str, str]]: List of dictionaries with 'url' and 'content' keys.
|
|
459
491
|
Returns empty list if no human-readable files are present.
|
|
460
|
-
|
|
492
|
+
|
|
461
493
|
Example:
|
|
462
494
|
>>> readable_files = task.get_human_readable_files()
|
|
463
495
|
>>> for file_data in readable_files:
|
|
@@ -466,14 +498,19 @@ class Task(XPanderSharedModel):
|
|
|
466
498
|
"""
|
|
467
499
|
if not self.input.files or len(self.input.files) == 0:
|
|
468
500
|
return []
|
|
469
|
-
|
|
501
|
+
|
|
470
502
|
categorized_files = categorize_files(file_urls=self.input.files)
|
|
471
|
-
|
|
503
|
+
|
|
472
504
|
if not categorized_files.files or len(categorized_files.files) == 0:
|
|
473
505
|
return []
|
|
474
506
|
|
|
475
|
-
return run_sync(
|
|
476
|
-
|
|
507
|
+
return run_sync(
|
|
508
|
+
fetch_urls(
|
|
509
|
+
urls=categorized_files.files,
|
|
510
|
+
disable_attachment_injection=self.disable_attachment_injection,
|
|
511
|
+
)
|
|
512
|
+
)
|
|
513
|
+
|
|
477
514
|
def to_message(self) -> str:
|
|
478
515
|
"""
|
|
479
516
|
Converts the input data into a formatted message string.
|
|
@@ -495,7 +532,7 @@ class Task(XPanderSharedModel):
|
|
|
495
532
|
if len(message) != 0:
|
|
496
533
|
message += "\n"
|
|
497
534
|
message += "Files: " + (", ".join(self.input.files))
|
|
498
|
-
|
|
535
|
+
|
|
499
536
|
# append human readable content like csv and such
|
|
500
537
|
readable_files = self.get_human_readable_files()
|
|
501
538
|
if readable_files and len(readable_files) != 0:
|
|
@@ -503,8 +540,83 @@ class Task(XPanderSharedModel):
|
|
|
503
540
|
for f in readable_files:
|
|
504
541
|
message += f"\n{json.dumps(f)}"
|
|
505
542
|
|
|
543
|
+
if self.deep_planning and self.deep_planning.enabled == True:
|
|
544
|
+
self.reload()
|
|
545
|
+
uncompleted_tasks = [task for task in self.deep_planning.tasks if not task.completed]
|
|
546
|
+
if len(uncompleted_tasks) != 0:
|
|
547
|
+
message = "\n".join([
|
|
548
|
+
"Task not finished, uncompleted tasks detected:",
|
|
549
|
+
f"Uncompleted tasks: {[task.model_dump_json() for task in uncompleted_tasks]}",
|
|
550
|
+
"You must complete tasks if fulfilled",
|
|
551
|
+
f"User's original request: \"{message}\""
|
|
552
|
+
])
|
|
553
|
+
|
|
506
554
|
return message
|
|
507
555
|
|
|
556
|
+
async def aget_activity_log(self) -> AgentActivityThread:
|
|
557
|
+
"""
|
|
558
|
+
Asynchronously retrieves the activity log for this task.
|
|
559
|
+
|
|
560
|
+
Fetches a detailed activity thread containing all messages, tool calls,
|
|
561
|
+
reasoning steps, sub-agent triggers, and authentication events that
|
|
562
|
+
occurred during the task execution.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
AgentActivityThread: Complete activity log including messages,
|
|
566
|
+
tool calls, reasoning, and other execution events.
|
|
567
|
+
|
|
568
|
+
Raises:
|
|
569
|
+
ModuleException: If the activity log cannot be retrieved or doesn't exist.
|
|
570
|
+
|
|
571
|
+
Example:
|
|
572
|
+
>>> task = await Task.aload(task_id="task_123")
|
|
573
|
+
>>> activity_log = await task.aget_activity_log()
|
|
574
|
+
>>> for message in activity_log.messages:
|
|
575
|
+
... print(f"{message.role}: {message.content.text}")
|
|
576
|
+
"""
|
|
577
|
+
try:
|
|
578
|
+
client = APIClient(configuration=self.configuration)
|
|
579
|
+
activity_log: AgentActivityThread = await client.make_request(
|
|
580
|
+
path=APIRoute.GetTaskActivityLog.format(
|
|
581
|
+
agent_id=self.agent_id, task_id=self.id
|
|
582
|
+
),
|
|
583
|
+
model=AgentActivityThread,
|
|
584
|
+
)
|
|
585
|
+
if not activity_log:
|
|
586
|
+
raise HTTPStatusError(404, "Log not found")
|
|
587
|
+
|
|
588
|
+
return activity_log
|
|
589
|
+
except HTTPStatusError as e:
|
|
590
|
+
raise ModuleException(
|
|
591
|
+
status_code=e.response.status_code, description=e.response.text
|
|
592
|
+
)
|
|
593
|
+
except Exception as e:
|
|
594
|
+
raise ModuleException(
|
|
595
|
+
status_code=500, description=f"Failed to get activity log - {str(e)}"
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
def get_activity_log(self) -> AgentActivityThread:
|
|
599
|
+
"""
|
|
600
|
+
Retrieves the activity log for this task synchronously.
|
|
601
|
+
|
|
602
|
+
This method wraps the asynchronous aget_activity_log method for use
|
|
603
|
+
in synchronous environments.
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
AgentActivityThread: Complete activity log including messages,
|
|
607
|
+
tool calls, reasoning, and other execution events.
|
|
608
|
+
|
|
609
|
+
Raises:
|
|
610
|
+
ModuleException: If the activity log cannot be retrieved or doesn't exist.
|
|
611
|
+
|
|
612
|
+
Example:
|
|
613
|
+
>>> task = Task.load(task_id="task_123")
|
|
614
|
+
>>> activity_log = task.get_activity_log()
|
|
615
|
+
>>> for message in activity_log.messages:
|
|
616
|
+
... print(f"{message.role}: {message.content.text}")
|
|
617
|
+
"""
|
|
618
|
+
return run_sync(self.aget_activity_log())
|
|
619
|
+
|
|
508
620
|
async def aevents(self) -> AsyncGenerator[TaskUpdateEvent, None]:
|
|
509
621
|
"""
|
|
510
622
|
Asynchronously streams task events.
|
|
@@ -541,10 +653,12 @@ class Task(XPanderSharedModel):
|
|
|
541
653
|
json_event_data: dict = json.loads(event.data)
|
|
542
654
|
if json_event_data.get("type", None).startswith("task"):
|
|
543
655
|
task_data = json_event_data.get("data")
|
|
544
|
-
json_event_data.pop("data")
|
|
656
|
+
json_event_data.pop("data") # delete data
|
|
545
657
|
yield TaskUpdateEvent(
|
|
546
658
|
**json_event_data,
|
|
547
|
-
data=Task(
|
|
659
|
+
data=Task(
|
|
660
|
+
**task_data, configuration=self.configuration
|
|
661
|
+
),
|
|
548
662
|
)
|
|
549
663
|
continue
|
|
550
664
|
except Exception:
|
|
@@ -585,16 +699,13 @@ class Task(XPanderSharedModel):
|
|
|
585
699
|
|
|
586
700
|
while queue:
|
|
587
701
|
yield queue.pop(0)
|
|
588
|
-
|
|
589
|
-
async def areport_metrics(
|
|
590
|
-
self,
|
|
591
|
-
configuration: Optional[Configuration] = None
|
|
592
|
-
):
|
|
702
|
+
|
|
703
|
+
async def areport_metrics(self, configuration: Optional[Configuration] = None):
|
|
593
704
|
"""
|
|
594
705
|
Asynchronously report LLM task metrics to xpander.ai.
|
|
595
706
|
|
|
596
707
|
Args:
|
|
597
|
-
configuration (Optional[Configuration], optional):
|
|
708
|
+
configuration (Optional[Configuration], optional):
|
|
598
709
|
API client configuration. If not provided, a new instance is created. Defaults to None.
|
|
599
710
|
|
|
600
711
|
Raises:
|
|
@@ -606,10 +717,10 @@ class Task(XPanderSharedModel):
|
|
|
606
717
|
try:
|
|
607
718
|
configuration = configuration or Configuration()
|
|
608
719
|
client = APIClient(configuration=configuration)
|
|
609
|
-
|
|
720
|
+
|
|
610
721
|
if not self.tokens:
|
|
611
722
|
raise ValueError("tokens must be provided. task.tokens = Tokens()")
|
|
612
|
-
|
|
723
|
+
|
|
613
724
|
task_report_request = ExecutionMetricsReport(
|
|
614
725
|
execution_id=self.id,
|
|
615
726
|
source=self.source,
|
|
@@ -621,38 +732,30 @@ class Task(XPanderSharedModel):
|
|
|
621
732
|
ai_model="xpander",
|
|
622
733
|
api_calls_made=self.used_tools,
|
|
623
734
|
result=self.result or None,
|
|
624
|
-
llm_tokens=ExecutionTokens(worker=self.tokens)
|
|
735
|
+
llm_tokens=ExecutionTokens(worker=self.tokens),
|
|
625
736
|
)
|
|
626
737
|
|
|
627
738
|
await client.make_request(
|
|
628
|
-
path=APIRoute.ReportExecutionMetrics.format(
|
|
629
|
-
agent_id=self.agent_id
|
|
630
|
-
),
|
|
739
|
+
path=APIRoute.ReportExecutionMetrics.format(agent_id=self.agent_id),
|
|
631
740
|
method="POST",
|
|
632
741
|
payload=task_report_request.model_dump_safe(),
|
|
633
742
|
)
|
|
634
743
|
|
|
635
744
|
except HTTPStatusError as e:
|
|
636
745
|
raise ModuleException(
|
|
637
|
-
status_code=e.response.status_code,
|
|
638
|
-
description=e.response.text
|
|
746
|
+
status_code=e.response.status_code, description=e.response.text
|
|
639
747
|
)
|
|
640
748
|
except Exception as e:
|
|
641
749
|
raise ModuleException(
|
|
642
|
-
status_code=500,
|
|
643
|
-
description=f"Failed to report metrics - {str(e)}"
|
|
750
|
+
status_code=500, description=f"Failed to report metrics - {str(e)}"
|
|
644
751
|
)
|
|
645
752
|
|
|
646
|
-
|
|
647
|
-
def report_metrics(
|
|
648
|
-
self,
|
|
649
|
-
configuration: Optional[Configuration] = None
|
|
650
|
-
):
|
|
753
|
+
def report_metrics(self, configuration: Optional[Configuration] = None):
|
|
651
754
|
"""
|
|
652
755
|
Report LLM task metrics to xpander.ai.
|
|
653
756
|
|
|
654
757
|
Args:
|
|
655
|
-
configuration (Optional[Configuration], optional):
|
|
758
|
+
configuration (Optional[Configuration], optional):
|
|
656
759
|
API client configuration. If not provided, a new instance is created. Defaults to None.
|
|
657
760
|
|
|
658
761
|
Raises:
|
|
@@ -661,15 +764,69 @@ class Task(XPanderSharedModel):
|
|
|
661
764
|
Returns:
|
|
662
765
|
None
|
|
663
766
|
"""
|
|
664
|
-
return run_sync(
|
|
665
|
-
self.areport_metrics(
|
|
666
|
-
configuration=configuration
|
|
667
|
-
)
|
|
668
|
-
)
|
|
767
|
+
return run_sync(self.areport_metrics(configuration=configuration))
|
|
669
768
|
|
|
769
|
+
async def aget_plan_following_status(self) -> PlanFollowingStatus:
|
|
770
|
+
"""
|
|
771
|
+
Asynchronously check if the task's deep planning is complete.
|
|
772
|
+
|
|
773
|
+
Reloads the task to get the latest deep planning state and checks for
|
|
774
|
+
any uncompleted tasks. If deep planning is disabled or all tasks are
|
|
775
|
+
completed, returns a status indicating the task can finish.
|
|
776
|
+
|
|
777
|
+
Returns:
|
|
778
|
+
PlanFollowingStatus: Status object containing:
|
|
779
|
+
- can_finish (bool): True if all tasks are completed or deep planning is disabled.
|
|
780
|
+
- uncompleted_tasks (List[DeepPlanningItem]): List of tasks not yet completed.
|
|
781
|
+
|
|
782
|
+
Example:
|
|
783
|
+
>>> status = await task.aget_plan_following_status()
|
|
784
|
+
>>> if not status.can_finish:
|
|
785
|
+
... print(f"Remaining tasks: {len(status.uncompleted_tasks)}")
|
|
786
|
+
"""
|
|
787
|
+
try:
|
|
788
|
+
if self.deep_planning and self.deep_planning.enabled and self.deep_planning.started and self.deep_planning.enforce:
|
|
789
|
+
await self.areload()
|
|
790
|
+
|
|
791
|
+
# allow early exit to ask question
|
|
792
|
+
if self.deep_planning.question_raised:
|
|
793
|
+
return PlanFollowingStatus(can_finish=True)
|
|
794
|
+
|
|
795
|
+
uncompleted_tasks = [
|
|
796
|
+
task for task in self.deep_planning.tasks if not task.completed
|
|
797
|
+
]
|
|
798
|
+
if len(uncompleted_tasks) != 0:
|
|
799
|
+
return PlanFollowingStatus(
|
|
800
|
+
can_finish=False, uncompleted_tasks=uncompleted_tasks
|
|
801
|
+
)
|
|
802
|
+
except Exception:
|
|
803
|
+
pass
|
|
804
|
+
|
|
805
|
+
return PlanFollowingStatus(can_finish=True)
|
|
806
|
+
|
|
807
|
+
def get_plan_following_status(self) -> PlanFollowingStatus:
|
|
808
|
+
"""
|
|
809
|
+
Check if the task's deep planning is complete synchronously.
|
|
810
|
+
|
|
811
|
+
This function wraps the asynchronous aget_plan_following_status method.
|
|
812
|
+
Reloads the task to get the latest deep planning state and checks for
|
|
813
|
+
any uncompleted tasks.
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
PlanFollowingStatus: Status object containing:
|
|
817
|
+
- can_finish (bool): True if all tasks are completed or deep planning is disabled.
|
|
818
|
+
- uncompleted_tasks (List[DeepPlanningItem]): List of tasks not yet completed.
|
|
819
|
+
|
|
820
|
+
Example:
|
|
821
|
+
>>> status = task.get_plan_following_status()
|
|
822
|
+
>>> if not status.can_finish:
|
|
823
|
+
... print(f"Remaining tasks: {len(status.uncompleted_tasks)}")
|
|
824
|
+
"""
|
|
825
|
+
return run_sync(self.aget_plan_following_status())
|
|
826
|
+
|
|
670
827
|
@classmethod
|
|
671
828
|
async def areport_external_task(
|
|
672
|
-
cls: Type[T],
|
|
829
|
+
cls: Type[T],
|
|
673
830
|
configuration: Optional[Configuration] = None,
|
|
674
831
|
agent_id: Optional[str] = None,
|
|
675
832
|
id: Optional[str] = None,
|
|
@@ -679,7 +836,7 @@ class Task(XPanderSharedModel):
|
|
|
679
836
|
is_success: Optional[bool] = True,
|
|
680
837
|
result: Optional[str] = None,
|
|
681
838
|
duration: Optional[float] = 0,
|
|
682
|
-
used_tools: Optional[List[str]] = []
|
|
839
|
+
used_tools: Optional[List[str]] = [],
|
|
683
840
|
) -> T:
|
|
684
841
|
"""
|
|
685
842
|
Asynchronously reports an external task to the xpander.ai backend.
|
|
@@ -687,7 +844,7 @@ class Task(XPanderSharedModel):
|
|
|
687
844
|
This method is used to report the result of a task that was executed
|
|
688
845
|
externally (outside the xpander.ai platform). It submits execution details,
|
|
689
846
|
including inputs, outputs, success status, and resource usage, to the backend.
|
|
690
|
-
|
|
847
|
+
|
|
691
848
|
Args:
|
|
692
849
|
configuration (Optional[Configuration]): Optional configuration for API calls.
|
|
693
850
|
agent_id (Optional[str]): Identifier of the agent associated with the task.
|
|
@@ -699,13 +856,13 @@ class Task(XPanderSharedModel):
|
|
|
699
856
|
result (Optional[str]): String representation of the final result.
|
|
700
857
|
duration (Optional[float]): Task execution duration, in seconds. Defaults to 0.
|
|
701
858
|
used_tools (Optional[List[str]]): List of tools used during the execution. Defaults to empty list.
|
|
702
|
-
|
|
859
|
+
|
|
703
860
|
Returns:
|
|
704
861
|
T: Instance of the Task class, representing the reported task.
|
|
705
|
-
|
|
862
|
+
|
|
706
863
|
Raises:
|
|
707
864
|
ModuleException: Raised on backend or network errors.
|
|
708
|
-
|
|
865
|
+
|
|
709
866
|
Example:
|
|
710
867
|
>>> task = await Task.areport_external_task(
|
|
711
868
|
... agent_id="agent_xyz",
|
|
@@ -725,7 +882,7 @@ class Task(XPanderSharedModel):
|
|
|
725
882
|
is_success=is_success,
|
|
726
883
|
result=result,
|
|
727
884
|
duration=duration,
|
|
728
|
-
used_tools=used_tools
|
|
885
|
+
used_tools=used_tools,
|
|
729
886
|
)
|
|
730
887
|
response_data = await client.make_request(
|
|
731
888
|
path=APIRoute.ReportExternalTask.format(agent_id=agent_id),
|
|
@@ -739,12 +896,13 @@ class Task(XPanderSharedModel):
|
|
|
739
896
|
)
|
|
740
897
|
except Exception as e:
|
|
741
898
|
raise ModuleException(
|
|
742
|
-
status_code=500,
|
|
899
|
+
status_code=500,
|
|
900
|
+
description=f"Failed to report external task - {str(e)}",
|
|
743
901
|
)
|
|
744
902
|
|
|
745
903
|
@classmethod
|
|
746
904
|
def report_external_task(
|
|
747
|
-
cls: Type[T],
|
|
905
|
+
cls: Type[T],
|
|
748
906
|
configuration: Optional[Configuration] = None,
|
|
749
907
|
agent_id: Optional[str] = None,
|
|
750
908
|
id: Optional[str] = None,
|
|
@@ -754,7 +912,7 @@ class Task(XPanderSharedModel):
|
|
|
754
912
|
is_success: Optional[bool] = True,
|
|
755
913
|
result: Optional[str] = None,
|
|
756
914
|
duration: Optional[float] = 0,
|
|
757
|
-
used_tools: Optional[List[str]] = []
|
|
915
|
+
used_tools: Optional[List[str]] = [],
|
|
758
916
|
) -> T:
|
|
759
917
|
"""
|
|
760
918
|
Synchronously reports an external task to the xpander.ai backend.
|
|
@@ -16,7 +16,7 @@ from xpander_sdk.models.configuration import Configuration
|
|
|
16
16
|
from xpander_sdk.models.shared import XPanderSharedModel
|
|
17
17
|
from xpander_sdk.modules.tools_repository.sub_modules.tool import Tool
|
|
18
18
|
from xpander_sdk.utils.event_loop import run_sync
|
|
19
|
-
|
|
19
|
+
import json
|
|
20
20
|
|
|
21
21
|
class ToolsRepository(XPanderSharedModel):
|
|
22
22
|
"""
|
|
@@ -51,7 +51,7 @@ class ToolsRepository(XPanderSharedModel):
|
|
|
51
51
|
tools: List[Tool] = []
|
|
52
52
|
|
|
53
53
|
agent_graph: Optional[Any] = None
|
|
54
|
-
is_async: Optional[bool] =
|
|
54
|
+
is_async: Optional[bool] = True
|
|
55
55
|
|
|
56
56
|
# Immutable registry for tools defined via decorator
|
|
57
57
|
_local_tools: ClassVar[List[Tool]] = []
|
|
@@ -158,6 +158,10 @@ class ToolsRepository(XPanderSharedModel):
|
|
|
158
158
|
fn_list = []
|
|
159
159
|
|
|
160
160
|
for tool in self.list:
|
|
161
|
+
|
|
162
|
+
# add json schema to the model doc
|
|
163
|
+
tool.schema.__doc__ = "Pay attention to the schema, dont miss. " + json.dumps(tool.schema.model_json_schema(mode="serialization"))
|
|
164
|
+
|
|
161
165
|
schema_cls: Type[BaseModel] = tool.schema
|
|
162
166
|
|
|
163
167
|
# Create closure to capture tool and schema_cls
|