xpander-sdk 1.60.8__py3-none-any.whl → 2.0.155__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 (90) hide show
  1. xpander_sdk/__init__.py +76 -7793
  2. xpander_sdk/consts/__init__.py +0 -0
  3. xpander_sdk/consts/api_routes.py +63 -0
  4. xpander_sdk/core/__init__.py +0 -0
  5. xpander_sdk/core/module_base.py +164 -0
  6. xpander_sdk/core/state.py +10 -0
  7. xpander_sdk/core/xpander_api_client.py +119 -0
  8. xpander_sdk/exceptions/__init__.py +0 -0
  9. xpander_sdk/exceptions/module_exception.py +45 -0
  10. xpander_sdk/models/__init__.py +0 -0
  11. xpander_sdk/models/activity.py +65 -0
  12. xpander_sdk/models/configuration.py +92 -0
  13. xpander_sdk/models/events.py +70 -0
  14. xpander_sdk/models/frameworks.py +64 -0
  15. xpander_sdk/models/shared.py +102 -0
  16. xpander_sdk/models/user.py +21 -0
  17. xpander_sdk/modules/__init__.py +0 -0
  18. xpander_sdk/modules/agents/__init__.py +0 -0
  19. xpander_sdk/modules/agents/agents_module.py +164 -0
  20. xpander_sdk/modules/agents/models/__init__.py +0 -0
  21. xpander_sdk/modules/agents/models/agent.py +477 -0
  22. xpander_sdk/modules/agents/models/agent_list.py +107 -0
  23. xpander_sdk/modules/agents/models/knowledge_bases.py +33 -0
  24. xpander_sdk/modules/agents/sub_modules/__init__.py +0 -0
  25. xpander_sdk/modules/agents/sub_modules/agent.py +953 -0
  26. xpander_sdk/modules/agents/utils/__init__.py +0 -0
  27. xpander_sdk/modules/agents/utils/generic.py +2 -0
  28. xpander_sdk/modules/backend/__init__.py +0 -0
  29. xpander_sdk/modules/backend/backend_module.py +425 -0
  30. xpander_sdk/modules/backend/frameworks/__init__.py +0 -0
  31. xpander_sdk/modules/backend/frameworks/agno.py +627 -0
  32. xpander_sdk/modules/backend/frameworks/dispatch.py +36 -0
  33. xpander_sdk/modules/backend/utils/__init__.py +0 -0
  34. xpander_sdk/modules/backend/utils/mcp_oauth.py +95 -0
  35. xpander_sdk/modules/events/__init__.py +0 -0
  36. xpander_sdk/modules/events/decorators/__init__.py +0 -0
  37. xpander_sdk/modules/events/decorators/on_boot.py +94 -0
  38. xpander_sdk/modules/events/decorators/on_shutdown.py +94 -0
  39. xpander_sdk/modules/events/decorators/on_task.py +203 -0
  40. xpander_sdk/modules/events/events_module.py +629 -0
  41. xpander_sdk/modules/events/models/__init__.py +0 -0
  42. xpander_sdk/modules/events/models/deployments.py +25 -0
  43. xpander_sdk/modules/events/models/events.py +57 -0
  44. xpander_sdk/modules/events/utils/__init__.py +0 -0
  45. xpander_sdk/modules/events/utils/generic.py +56 -0
  46. xpander_sdk/modules/events/utils/git_init.py +32 -0
  47. xpander_sdk/modules/knowledge_bases/__init__.py +0 -0
  48. xpander_sdk/modules/knowledge_bases/knowledge_bases_module.py +217 -0
  49. xpander_sdk/modules/knowledge_bases/models/__init__.py +0 -0
  50. xpander_sdk/modules/knowledge_bases/models/knowledge_bases.py +11 -0
  51. xpander_sdk/modules/knowledge_bases/sub_modules/__init__.py +0 -0
  52. xpander_sdk/modules/knowledge_bases/sub_modules/knowledge_base.py +107 -0
  53. xpander_sdk/modules/knowledge_bases/sub_modules/knowledge_base_document_item.py +40 -0
  54. xpander_sdk/modules/knowledge_bases/utils/__init__.py +0 -0
  55. xpander_sdk/modules/tasks/__init__.py +0 -0
  56. xpander_sdk/modules/tasks/models/__init__.py +0 -0
  57. xpander_sdk/modules/tasks/models/task.py +153 -0
  58. xpander_sdk/modules/tasks/models/tasks_list.py +107 -0
  59. xpander_sdk/modules/tasks/sub_modules/__init__.py +0 -0
  60. xpander_sdk/modules/tasks/sub_modules/task.py +887 -0
  61. xpander_sdk/modules/tasks/tasks_module.py +492 -0
  62. xpander_sdk/modules/tasks/utils/__init__.py +0 -0
  63. xpander_sdk/modules/tasks/utils/files.py +114 -0
  64. xpander_sdk/modules/tools_repository/__init__.py +0 -0
  65. xpander_sdk/modules/tools_repository/decorators/__init__.py +0 -0
  66. xpander_sdk/modules/tools_repository/decorators/register_tool.py +108 -0
  67. xpander_sdk/modules/tools_repository/models/__init__.py +0 -0
  68. xpander_sdk/modules/tools_repository/models/mcp.py +68 -0
  69. xpander_sdk/modules/tools_repository/models/tool_invocation_result.py +14 -0
  70. xpander_sdk/modules/tools_repository/sub_modules/__init__.py +0 -0
  71. xpander_sdk/modules/tools_repository/sub_modules/tool.py +578 -0
  72. xpander_sdk/modules/tools_repository/tools_repository_module.py +259 -0
  73. xpander_sdk/modules/tools_repository/utils/__init__.py +0 -0
  74. xpander_sdk/modules/tools_repository/utils/generic.py +57 -0
  75. xpander_sdk/modules/tools_repository/utils/local_tools.py +52 -0
  76. xpander_sdk/modules/tools_repository/utils/schemas.py +308 -0
  77. xpander_sdk/utils/__init__.py +0 -0
  78. xpander_sdk/utils/env.py +44 -0
  79. xpander_sdk/utils/event_loop.py +67 -0
  80. xpander_sdk/utils/tools.py +32 -0
  81. xpander_sdk-2.0.155.dist-info/METADATA +538 -0
  82. xpander_sdk-2.0.155.dist-info/RECORD +85 -0
  83. {xpander_sdk-1.60.8.dist-info → xpander_sdk-2.0.155.dist-info}/WHEEL +1 -1
  84. {xpander_sdk-1.60.8.dist-info → xpander_sdk-2.0.155.dist-info/licenses}/LICENSE +0 -1
  85. xpander_sdk/_jsii/__init__.py +0 -39
  86. xpander_sdk/_jsii/xpander-sdk@1.60.8.jsii.tgz +0 -0
  87. xpander_sdk/py.typed +0 -1
  88. xpander_sdk-1.60.8.dist-info/METADATA +0 -368
  89. xpander_sdk-1.60.8.dist-info/RECORD +0 -9
  90. {xpander_sdk-1.60.8.dist-info → xpander_sdk-2.0.155.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,887 @@
1
+ """
2
+ Task Management Module for xpander.ai SDK.
3
+
4
+ This module provides the Task class which serves as the primary interface
5
+ for managing task execution within the xpander.ai platform. It handles
6
+ task lifecycle operations including loading, saving, status updates,
7
+ and termination.
8
+
9
+ The Task class integrates with the xpander.ai Backend-as-a-Service (BaaS)
10
+ platform to provide seamless task management capabilities with both
11
+ synchronous and asynchronous operations.
12
+
13
+ Typical usage example:
14
+ >>> from xpander_sdk.modules.tasks.sub_modules.task import Task
15
+ >>> from xpander_sdk.modules.tasks.models.task import AgentExecutionStatus
16
+
17
+ >>> # Load an existing task
18
+ >>> task = Task.load(task_id="task_123")
19
+
20
+ >>> # Update task status
21
+ >>> task.set_status(AgentExecutionStatus.Running)
22
+
23
+ >>> # Save changes
24
+ >>> task.save()
25
+
26
+ >>> # Stop the task when needed
27
+ >>> task.stop()
28
+ """
29
+
30
+ from datetime import datetime
31
+ from typing import (
32
+ Any,
33
+ AsyncGenerator,
34
+ Dict,
35
+ Generator,
36
+ List,
37
+ Optional,
38
+ Type,
39
+ TypeVar,
40
+ Union,
41
+ )
42
+ from httpx import HTTPStatusError
43
+ import httpx
44
+ import json
45
+ from httpx_sse import aconnect_sse
46
+
47
+ from xpander_sdk.consts.api_routes import APIRoute
48
+ from xpander_sdk.core.xpander_api_client import APIClient
49
+ from xpander_sdk.exceptions.module_exception import ModuleException
50
+ from xpander_sdk.models.activity import AgentActivityThread
51
+ from xpander_sdk.models.configuration import Configuration
52
+ from xpander_sdk.models.events import (
53
+ TaskUpdateEventType,
54
+ ToolCallRequest,
55
+ ToolCallResult,
56
+ )
57
+ from xpander_sdk.models.shared import (
58
+ ExecutionTokens,
59
+ OutputFormat,
60
+ ThinkMode,
61
+ Tokens,
62
+ XPanderSharedModel,
63
+ )
64
+ from xpander_sdk.modules.events.utils.generic import get_events_base, get_events_headers
65
+ from xpander_sdk.modules.tasks.models.task import (
66
+ AgentExecutionInput,
67
+ AgentExecutionStatus,
68
+ HumanInTheLoop,
69
+ ExecutionMetricsReport,
70
+ PendingECARequest,
71
+ TaskReportRequest,
72
+ )
73
+ from xpander_sdk.modules.tasks.utils.files import (
74
+ categorize_files,
75
+ fetch_urls,
76
+ fetch_file,
77
+ )
78
+ from xpander_sdk.modules.tools_repository.models.mcp import (
79
+ MCPOAuthGetTokenResponse,
80
+ MCPServerDetails,
81
+ )
82
+ from xpander_sdk.utils.event_loop import run_sync
83
+
84
+ # Type variable for Task class methods
85
+ T = TypeVar("T", bound="Task")
86
+
87
+ TaskUpdateEventData = Union[
88
+ T, ToolCallRequest, ToolCallResult, MCPOAuthGetTokenResponse
89
+ ]
90
+
91
+
92
+ class TaskUpdateEvent(XPanderSharedModel):
93
+ type: TaskUpdateEventType
94
+ task_id: str
95
+ organization_id: str
96
+ time: datetime
97
+ data: TaskUpdateEventData
98
+
99
+
100
+ class Task(XPanderSharedModel):
101
+ """
102
+ Represents a task entity in the xpander.ai platform.
103
+
104
+ This class manages task-related operations, including state updates,
105
+ API interaction, status control, and lifecycle management.
106
+
107
+ Attributes:
108
+ configuration (Optional[Configuration]): The current configuration settings for the task.
109
+ id (str): Unique identifier for the task.
110
+ agent_id (str): Identifier for the associated agent.
111
+ organization_id (str): Identifier for the organization.
112
+ input (AgentExecutionInput): The input parameters for agent execution.
113
+ status (Optional[AgentExecutionStatus]): Current status of the task.
114
+ last_executed_node_id (Optional[str]): ID of the last executed node.
115
+ agent_version (Optional[str]): Version of the agent.
116
+ created_at (datetime): Timestamp when the task was created.
117
+ started_at (Optional[datetime]): Timestamp when the task was started.
118
+ paused_at (Optional[datetime]): Timestamp when the task was paused.
119
+ finished_at (Optional[datetime]): Timestamp when the task was finished.
120
+ result (Optional[str]): Result of the task execution.
121
+ parent_execution (Optional[str]): Parent execution ID, if applicable.
122
+ sub_executions (Optional[List[str]]): List of sub-execution IDs.
123
+ is_manually_stopped (Optional[bool]): Flag indicating if the task was manually stopped.
124
+ payload_extension (Optional[dict]): Additional data for the task.
125
+ hitl_request (Optional[HumanInTheLoop]): Human-in-the-loop request state.
126
+ pending_eca_request (Optional[PendingECARequest]): Pending ECA request, if any.
127
+ source (Optional[str]): Source information of the task.
128
+ output_format (Optional[OutputFormat]): Desired output format of the task.
129
+ output_schema (Optional[Dict]): Schema for the task's output.
130
+ events_streaming (Optional[bool]): Flag indicating if the task has events streaming.
131
+ additional_context (Optional[str]): Additional context to be passed to the agent.
132
+ expected_output (Optional[str]): Expected output of the execution.
133
+ mcp_servers (Optional[List[MCPServerDetails]]): Optional list of mcp servers to use.
134
+ triggering_agent_id (Optional[str]): Optional triggering agent id.
135
+ title (Optional[str]): Optional task title.
136
+
137
+ Example:
138
+ >>> task = Task.load(task_id="task_123")
139
+ >>> task.set_status(AgentExecutionStatus.Running)
140
+ >>> task.save()
141
+ >>>
142
+ >>> # Get files for Agno integration
143
+ >>> files = task.get_files() # PDF files as Agno File objects
144
+ >>> images = task.get_images() # Image files as Agno Image objects
145
+ >>> result = await agno_agent.arun(
146
+ ... input=task.to_message(),
147
+ ... files=files,
148
+ ... images=images
149
+ ... )
150
+ """
151
+
152
+ configuration: Optional[Configuration] = None
153
+ id: str
154
+ agent_id: str
155
+ organization_id: str
156
+ input: AgentExecutionInput
157
+ status: Optional[AgentExecutionStatus] = AgentExecutionStatus.Pending
158
+ internal_status: Optional[str] = None
159
+ last_executed_node_id: Optional[str] = None
160
+ agent_version: Optional[str] = None
161
+ created_at: datetime
162
+ started_at: Optional[datetime] = None
163
+ paused_at: Optional[datetime] = None
164
+ finished_at: Optional[datetime] = None
165
+ result: Optional[str] = None
166
+ parent_execution: Optional[str] = None
167
+ sub_executions: Optional[List[str]] = []
168
+ is_manually_stopped: Optional[bool] = False
169
+ payload_extension: Optional[dict] = None
170
+ hitl_request: Optional[HumanInTheLoop] = None
171
+ pending_eca_request: Optional[PendingECARequest] = None
172
+ source: Optional[str] = None
173
+ output_format: Optional[OutputFormat] = None
174
+ output_schema: Optional[Dict] = None
175
+ events_streaming: Optional[bool] = False
176
+ additional_context: Optional[str] = None
177
+ expected_output: Optional[str] = (None,)
178
+ mcp_servers: Optional[List[MCPServerDetails]] = ([],)
179
+ triggering_agent_id: Optional[str] = (None,)
180
+ title: Optional[str] = (None,)
181
+ think_mode: Optional[ThinkMode] = ThinkMode.Default
182
+ disable_attachment_injection: Optional[bool] = False
183
+
184
+ # metrics
185
+ tokens: Optional[Tokens] = None
186
+ used_tools: Optional[List[str]] = []
187
+ duration: Optional[float] = 0
188
+
189
+ def model_post_init(self, context):
190
+ """
191
+ Post-initialization hook for the model.
192
+
193
+ This method is called after the model is initialized. It sets the current
194
+ task state and then calls the parent class's `model_post_init` method.
195
+
196
+ Parameters:
197
+ context (Any): Context object provided during model initialization.
198
+
199
+ Returns:
200
+ Any: The result from the superclass `model_post_init` method.
201
+
202
+ Note:
203
+ This method uses `self.configuration.state.task = self` to register the current task
204
+ in the global state.
205
+
206
+ Powered by xpander.ai
207
+ """
208
+ self.configuration.state.task = self
209
+ return super().model_post_init(context)
210
+
211
+ async def areload(self):
212
+ """
213
+ Reload the current object asynchronously.
214
+
215
+ This method fetches a new instance of the object from the data source
216
+ using the current object's `id` and `configuration`, then updates the
217
+ current object's attributes with the new instance's attributes.
218
+
219
+ Returns:
220
+ self: The reloaded instance of the object.
221
+
222
+ Powered by xpander.ai
223
+ """
224
+ new_obj = await self.aload(
225
+ task_id=self.id,
226
+ configuration=self.configuration,
227
+ )
228
+ self.__dict__.update(new_obj.__dict__)
229
+ return self
230
+
231
+ def reload(self):
232
+ """
233
+ Reload the current object synchronously.
234
+
235
+ This method runs the asynchronous `areload` method synchronously
236
+ to update the current object's attributes.
237
+
238
+ Returns:
239
+ self: The reloaded instance of the object.
240
+
241
+ Powered by xpander.ai
242
+ """
243
+ run_sync(self.areload())
244
+
245
+ @classmethod
246
+ async def aload(
247
+ cls: Type[T], task_id: str, configuration: Optional[Configuration] = None
248
+ ) -> T:
249
+ """
250
+ Asynchronously loads a task by its ID.
251
+
252
+ Args:
253
+ task_id (str): Unique identifier for the task to load.
254
+ configuration (Optional[Configuration]): Configuration settings for API interaction.
255
+
256
+ Returns:
257
+ T: Instance of the Task class.
258
+
259
+ Raises:
260
+ ModuleException: Error related to HTTP requests or task fetching.
261
+
262
+ Example:
263
+ >>> task = await Task.aload(task_id="task_123")
264
+ """
265
+ try:
266
+ client = APIClient(configuration=configuration)
267
+ response_data = await client.make_request(
268
+ path=APIRoute.GetTask.format(task_id=task_id)
269
+ )
270
+ task = cls.model_validate(
271
+ {**response_data, "configuration": configuration or Configuration()}
272
+ )
273
+ return task
274
+ except HTTPStatusError as e:
275
+ raise ModuleException(
276
+ status_code=e.response.status_code, description=e.response.text
277
+ )
278
+ except Exception as e:
279
+ raise ModuleException(
280
+ status_code=500, description=f"Failed to get tas - {str(e)}"
281
+ )
282
+
283
+ @classmethod
284
+ def load(
285
+ cls: Type[T], task_id: str, configuration: Optional[Configuration] = None
286
+ ) -> T:
287
+ """
288
+ Loads a task by its ID synchronously.
289
+
290
+ This function wraps the asynchronous aload method.
291
+
292
+ Args:
293
+ task_id (str): Unique identifier for the task to load.
294
+ configuration (Optional[Configuration]): Configuration settings for API interaction.
295
+
296
+ Returns:
297
+ T: Instance of the Task class.
298
+
299
+ Example:
300
+ >>> task = Task.load(task_id="task_123")
301
+ """
302
+ return run_sync(cls.aload(task_id=task_id, configuration=configuration))
303
+
304
+ async def aset_status(
305
+ self, status: AgentExecutionStatus, result: Optional[str] = None
306
+ ):
307
+ """
308
+ Asynchronously sets the task status and updates it in the backend.
309
+
310
+ Args:
311
+ status (AgentExecutionStatus): The new status to apply.
312
+ result (str): The result to apply.
313
+
314
+ Example:
315
+ >>> await task.aset_status(AgentExecutionStatus.Running, "task started")
316
+ """
317
+ self.status = status
318
+ self.result = result
319
+ await self.asave()
320
+
321
+ def set_status(self, status: AgentExecutionStatus, result: Optional[str] = None):
322
+ """
323
+ Sets the task status synchronously.
324
+
325
+ This function wraps the asynchronous aset_status method.
326
+
327
+ Args:
328
+ status (AgentExecutionStatus): The new status to apply.
329
+ result (str): The result to apply.
330
+
331
+ Example:
332
+ >>> task.set_status(AgentExecutionStatus.Running, "task started")
333
+ """
334
+ return run_sync(self.aset_status(status=status, result=result))
335
+
336
+ async def asave(self):
337
+ """
338
+ Asynchronously saves the current task state to the backend.
339
+
340
+ Raises:
341
+ ModuleException: Error related to HTTP requests or task saving.
342
+
343
+ Example:
344
+ >>> await task.asave()
345
+ """
346
+ client = APIClient(configuration=self.configuration)
347
+ try:
348
+ response = await client.make_request(
349
+ path=APIRoute.UpdateTask.format(task_id=self.id),
350
+ method="PATCH",
351
+ payload=self.model_dump_safe(),
352
+ )
353
+ updated_task = Task(**response, configuration=self.configuration)
354
+ for field, value in updated_task.__dict__.items():
355
+ setattr(self, field, value)
356
+ except HTTPStatusError as e:
357
+ raise ModuleException(e.response.status_code, e.response.text)
358
+ except Exception as e:
359
+ raise ModuleException(500, f"Failed to save task: {str(e)}")
360
+
361
+ def save(self):
362
+ """
363
+ Saves the current task state synchronously.
364
+
365
+ This function wraps the asynchronous asave method.
366
+
367
+ Example:
368
+ >>> task.save()
369
+ """
370
+ return run_sync(self.asave())
371
+
372
+ async def astop(self):
373
+ """
374
+ Asynchronously stops the task.
375
+
376
+ Communicates with the backend to terminate the task execution.
377
+
378
+ Raises:
379
+ ModuleException: Error related to HTTP requests or task stopping.
380
+
381
+ Example:
382
+ >>> await task.astop()
383
+ """
384
+ client = APIClient(configuration=self.configuration)
385
+ try:
386
+ response = await client.make_request(
387
+ path=APIRoute.TaskCrud.format(agent_or_task_id=self.id), method="DELETE"
388
+ )
389
+ updated_task = Task(**response, configuration=self.configuration)
390
+ for field, value in updated_task.__dict__.items():
391
+ setattr(self, field, value)
392
+ except HTTPStatusError as e:
393
+ raise ModuleException(e.response.status_code, e.response.text)
394
+ except Exception as e:
395
+ raise ModuleException(500, f"Failed to stop task: {str(e)}")
396
+
397
+ def stop(self):
398
+ """
399
+ Stops the task synchronously.
400
+
401
+ This function wraps the asynchronous astop method.
402
+
403
+ Example:
404
+ >>> task.stop()
405
+ """
406
+ return run_sync(self.astop())
407
+
408
+ def get_files(self) -> list[Any]:
409
+ """
410
+ Get PDF files from task input, formatted for Agno integration.
411
+
412
+ Returns PDF files as Agno File objects when the Agno framework is available,
413
+ or as URL strings otherwise. This method is designed for seamless integration
414
+ with Agno agents.
415
+
416
+ Returns:
417
+ list[Any]: List of File objects (when Agno is available) or URL strings.
418
+ Returns empty list if no PDF files are present in task input.
419
+
420
+ Example:
421
+ >>> files = task.get_files()
422
+ >>> result = await agno_agent.arun(
423
+ ... input=task.to_message(),
424
+ ... files=files
425
+ ... )
426
+ """
427
+
428
+ if not self.input.files or len(self.input.files) == 0:
429
+ return []
430
+
431
+ categorized_files = categorize_files(file_urls=self.input.files)
432
+
433
+ if not categorized_files.pdfs or len(categorized_files.pdfs) == 0:
434
+ return []
435
+
436
+ try:
437
+ from agno.media import File # test import
438
+
439
+ return [fetch_file(url=url) for url in categorized_files.pdfs]
440
+ except Exception:
441
+ return categorized_files.pdfs
442
+
443
+ def get_images(self) -> list[Any]:
444
+ """
445
+ Get image files from task input, formatted for Agno integration.
446
+
447
+ Returns image files as Agno Image objects when the Agno framework is available,
448
+ or as URL strings otherwise. This method is designed for seamless integration
449
+ with Agno agents that support image processing.
450
+
451
+ Returns:
452
+ list[Any]: List of Image objects (when Agno is available) or URL strings.
453
+ Returns empty list if no image files are present in task input.
454
+
455
+ Example:
456
+ >>> images = task.get_images()
457
+ >>> result = await agno_agent.arun(
458
+ ... input=task.to_message(),
459
+ ... images=images
460
+ ... )
461
+ """
462
+ if not self.input.files or len(self.input.files) == 0:
463
+ return []
464
+
465
+ categorized_files = categorize_files(file_urls=self.input.files)
466
+
467
+ if not categorized_files.images or len(categorized_files.images) == 0:
468
+ return []
469
+
470
+ try:
471
+ from agno.media import Image
472
+
473
+ return [Image(url=url) for url in categorized_files.images]
474
+ except Exception:
475
+ return categorized_files.images
476
+
477
+ def get_human_readable_files(self) -> list[Any]:
478
+ """
479
+ Get human-readable files from task input with their content.
480
+
481
+ Returns text-based files (like .txt, .csv, .json, .py, etc.) with their content
482
+ fetched and parsed. This method is automatically used by to_message() to include
483
+ file contents in the task message.
484
+
485
+ Returns:
486
+ list[dict[str, str]]: List of dictionaries with 'url' and 'content' keys.
487
+ Returns empty list if no human-readable files are present.
488
+
489
+ Example:
490
+ >>> readable_files = task.get_human_readable_files()
491
+ >>> for file_data in readable_files:
492
+ ... print(f"File: {file_data['url']}")
493
+ ... print(f"Content: {file_data['content']}")
494
+ """
495
+ if not self.input.files or len(self.input.files) == 0:
496
+ return []
497
+
498
+ categorized_files = categorize_files(file_urls=self.input.files)
499
+
500
+ if not categorized_files.files or len(categorized_files.files) == 0:
501
+ return []
502
+
503
+ return run_sync(
504
+ fetch_urls(
505
+ urls=categorized_files.files,
506
+ disable_attachment_injection=self.disable_attachment_injection,
507
+ )
508
+ )
509
+
510
+ def to_message(self) -> str:
511
+ """
512
+ Converts the input data into a formatted message string.
513
+
514
+ This method constructs a message from text and file inputs.
515
+ If text exists, it is included first. If files are present,
516
+ they are appended as a comma-separated list under "Files:".
517
+
518
+ Returns:
519
+ str: A formatted message string including text and/or file names.
520
+
521
+ Powered by xpander.ai
522
+ """
523
+ message = ""
524
+ if self.input.text:
525
+ message = self.input.text
526
+
527
+ if self.input.files and len(self.input.files) != 0:
528
+ if len(message) != 0:
529
+ message += "\n"
530
+ message += "Files: " + (", ".join(self.input.files))
531
+
532
+ # append human readable content like csv and such
533
+ readable_files = self.get_human_readable_files()
534
+ if readable_files and len(readable_files) != 0:
535
+ message += "\nFiles contents:"
536
+ for f in readable_files:
537
+ message += f"\n{json.dumps(f)}"
538
+
539
+ return message
540
+
541
+ async def aget_activity_log(self) -> AgentActivityThread:
542
+ """
543
+ Asynchronously retrieves the activity log for this task.
544
+
545
+ Fetches a detailed activity thread containing all messages, tool calls,
546
+ reasoning steps, sub-agent triggers, and authentication events that
547
+ occurred during the task execution.
548
+
549
+ Returns:
550
+ AgentActivityThread: Complete activity log including messages,
551
+ tool calls, reasoning, and other execution events.
552
+
553
+ Raises:
554
+ ModuleException: If the activity log cannot be retrieved or doesn't exist.
555
+
556
+ Example:
557
+ >>> task = await Task.aload(task_id="task_123")
558
+ >>> activity_log = await task.aget_activity_log()
559
+ >>> for message in activity_log.messages:
560
+ ... print(f"{message.role}: {message.content.text}")
561
+ """
562
+ try:
563
+ client = APIClient(configuration=self.configuration)
564
+ activity_log: AgentActivityThread = await client.make_request(
565
+ path=APIRoute.GetTaskActivityLog.format(
566
+ agent_id=self.agent_id, task_id=self.id
567
+ ),
568
+ model=AgentActivityThread,
569
+ )
570
+ if not activity_log:
571
+ raise HTTPStatusError(404, "Log not found")
572
+
573
+ return activity_log
574
+ except HTTPStatusError as e:
575
+ raise ModuleException(
576
+ status_code=e.response.status_code, description=e.response.text
577
+ )
578
+ except Exception as e:
579
+ raise ModuleException(
580
+ status_code=500, description=f"Failed to get activity log - {str(e)}"
581
+ )
582
+
583
+ def get_activity_log(self) -> AgentActivityThread:
584
+ """
585
+ Retrieves the activity log for this task synchronously.
586
+
587
+ This method wraps the asynchronous aget_activity_log method for use
588
+ in synchronous environments.
589
+
590
+ Returns:
591
+ AgentActivityThread: Complete activity log including messages,
592
+ tool calls, reasoning, and other execution events.
593
+
594
+ Raises:
595
+ ModuleException: If the activity log cannot be retrieved or doesn't exist.
596
+
597
+ Example:
598
+ >>> task = Task.load(task_id="task_123")
599
+ >>> activity_log = task.get_activity_log()
600
+ >>> for message in activity_log.messages:
601
+ ... print(f"{message.role}: {message.content.text}")
602
+ """
603
+ return run_sync(self.aget_activity_log())
604
+
605
+ async def aevents(self) -> AsyncGenerator[TaskUpdateEvent, None]:
606
+ """
607
+ Asynchronously streams task events.
608
+
609
+ This method connects to the xpander.ai event stream using Server-Sent Events (SSE)
610
+ and yields each `TaskUpdateEvent` related to the current task as they arrive.
611
+
612
+ Requires that the task was created or loaded with `events_streaming=True`.
613
+
614
+ Yields:
615
+ TaskUpdateEvent: A parsed event containing real-time updates about the task.
616
+
617
+ Raises:
618
+ ValueError: If the task is not configured for event streaming.
619
+
620
+ Example:
621
+ >>> async for event in task.aevents():
622
+ >>> print(event)
623
+ """
624
+ if not self.events_streaming:
625
+ raise ValueError(f"Task {self.id} does not set with events streaming")
626
+
627
+ headers = get_events_headers(configuration=self.configuration)
628
+ url = get_events_base(configuration=self.configuration).replace(
629
+ "/events", f"/agent-execution/{self.id}/events"
630
+ )
631
+
632
+ async with httpx.AsyncClient(timeout=None, headers=headers) as client:
633
+ async with aconnect_sse(client, method="GET", url=url) as sse:
634
+ async for event in sse.aiter_sse():
635
+ # parse task and add configuration
636
+ if event.data:
637
+ try:
638
+ json_event_data: dict = json.loads(event.data)
639
+ if json_event_data.get("type", None).startswith("task"):
640
+ task_data = json_event_data.get("data")
641
+ json_event_data.pop("data") # delete data
642
+ yield TaskUpdateEvent(
643
+ **json_event_data,
644
+ data=Task(
645
+ **task_data, configuration=self.configuration
646
+ ),
647
+ )
648
+ continue
649
+ except Exception:
650
+ pass
651
+ yield TaskUpdateEvent.model_validate_json(event.data)
652
+
653
+ def events(self) -> Generator[TaskUpdateEvent, None, None]:
654
+ """
655
+ Synchronously streams task events.
656
+
657
+ This method wraps the asynchronous `aevents` generator and yields events in a
658
+ blocking manner for use in synchronous environments.
659
+
660
+ Requires that the task was created or loaded with `events_streaming=True`.
661
+
662
+ Yields:
663
+ TaskUpdateEvent: A parsed event containing real-time updates about the task.
664
+
665
+ Raises:
666
+ ValueError: If the task is not configured for event streaming.
667
+
668
+ Example:
669
+ >>> for event in task.events():
670
+ >>> print(event)
671
+ """
672
+
673
+ async def _consume():
674
+ async for event in self.aevents():
675
+ yield event
676
+
677
+ queue = []
678
+
679
+ async def _run():
680
+ async for item in _consume():
681
+ queue.append(item)
682
+
683
+ run_sync(_run())
684
+
685
+ while queue:
686
+ yield queue.pop(0)
687
+
688
+ async def areport_metrics(self, configuration: Optional[Configuration] = None):
689
+ """
690
+ Asynchronously report LLM task metrics to xpander.ai.
691
+
692
+ Args:
693
+ configuration (Optional[Configuration], optional):
694
+ API client configuration. If not provided, a new instance is created. Defaults to None.
695
+
696
+ Raises:
697
+ ModuleException: If the request fails due to an HTTP or unexpected error.
698
+
699
+ Returns:
700
+ None
701
+ """
702
+ try:
703
+ configuration = configuration or Configuration()
704
+ client = APIClient(configuration=configuration)
705
+
706
+ if not self.tokens:
707
+ raise ValueError("tokens must be provided. task.tokens = Tokens()")
708
+
709
+ task_report_request = ExecutionMetricsReport(
710
+ execution_id=self.id,
711
+ source=self.source,
712
+ memory_thread_id=self.id,
713
+ task=self.input.text or "",
714
+ status=self.status.value,
715
+ internal_status=self.internal_status,
716
+ duration=0.0,
717
+ ai_model="xpander",
718
+ api_calls_made=self.used_tools,
719
+ result=self.result or None,
720
+ llm_tokens=ExecutionTokens(worker=self.tokens),
721
+ )
722
+
723
+ await client.make_request(
724
+ path=APIRoute.ReportExecutionMetrics.format(agent_id=self.agent_id),
725
+ method="POST",
726
+ payload=task_report_request.model_dump_safe(),
727
+ )
728
+
729
+ except HTTPStatusError as e:
730
+ raise ModuleException(
731
+ status_code=e.response.status_code, description=e.response.text
732
+ )
733
+ except Exception as e:
734
+ raise ModuleException(
735
+ status_code=500, description=f"Failed to report metrics - {str(e)}"
736
+ )
737
+
738
+ def report_metrics(self, configuration: Optional[Configuration] = None):
739
+ """
740
+ Report LLM task metrics to xpander.ai.
741
+
742
+ Args:
743
+ configuration (Optional[Configuration], optional):
744
+ API client configuration. If not provided, a new instance is created. Defaults to None.
745
+
746
+ Raises:
747
+ ModuleException: If the request fails due to an HTTP or unexpected error.
748
+
749
+ Returns:
750
+ None
751
+ """
752
+ return run_sync(self.areport_metrics(configuration=configuration))
753
+
754
+ @classmethod
755
+ async def areport_external_task(
756
+ cls: Type[T],
757
+ configuration: Optional[Configuration] = None,
758
+ agent_id: Optional[str] = None,
759
+ id: Optional[str] = None,
760
+ input: Optional[str] = None,
761
+ llm_response: Optional[Any] = None,
762
+ tokens: Optional[Tokens] = None,
763
+ is_success: Optional[bool] = True,
764
+ result: Optional[str] = None,
765
+ duration: Optional[float] = 0,
766
+ used_tools: Optional[List[str]] = [],
767
+ ) -> T:
768
+ """
769
+ Asynchronously reports an external task to the xpander.ai backend.
770
+
771
+ This method is used to report the result of a task that was executed
772
+ externally (outside the xpander.ai platform). It submits execution details,
773
+ including inputs, outputs, success status, and resource usage, to the backend.
774
+
775
+ Args:
776
+ configuration (Optional[Configuration]): Optional configuration for API calls.
777
+ agent_id (Optional[str]): Identifier of the agent associated with the task.
778
+ id (Optional[str]): Task identifier (external or internal).
779
+ input (Optional[str]): The input parameters or data used in execution.
780
+ llm_response (Optional[Any]): The raw LLM response or relevant output object.
781
+ tokens (Optional[Tokens]): Tokens used in the execution.
782
+ is_success (Optional[bool]): Whether the task execution was successful. Defaults to True.
783
+ result (Optional[str]): String representation of the final result.
784
+ duration (Optional[float]): Task execution duration, in seconds. Defaults to 0.
785
+ used_tools (Optional[List[str]]): List of tools used during the execution. Defaults to empty list.
786
+
787
+ Returns:
788
+ T: Instance of the Task class, representing the reported task.
789
+
790
+ Raises:
791
+ ModuleException: Raised on backend or network errors.
792
+
793
+ Example:
794
+ >>> task = await Task.areport_external_task(
795
+ ... agent_id="agent_xyz",
796
+ ... id="external_task_123",
797
+ ... input="User message",
798
+ ... result="Done"
799
+ ... )
800
+ """
801
+ try:
802
+ configuration = configuration or Configuration()
803
+ client = APIClient(configuration=configuration)
804
+ task_report_request = TaskReportRequest(
805
+ id=id,
806
+ input=input,
807
+ llm_response=llm_response,
808
+ tokens=tokens,
809
+ is_success=is_success,
810
+ result=result,
811
+ duration=duration,
812
+ used_tools=used_tools,
813
+ )
814
+ response_data = await client.make_request(
815
+ path=APIRoute.ReportExternalTask.format(agent_id=agent_id),
816
+ method="POST",
817
+ payload=task_report_request.model_dump_safe(),
818
+ )
819
+ return cls.model_validate({**response_data, "configuration": configuration})
820
+ except HTTPStatusError as e:
821
+ raise ModuleException(
822
+ status_code=e.response.status_code, description=e.response.text
823
+ )
824
+ except Exception as e:
825
+ raise ModuleException(
826
+ status_code=500,
827
+ description=f"Failed to report external task - {str(e)}",
828
+ )
829
+
830
+ @classmethod
831
+ def report_external_task(
832
+ cls: Type[T],
833
+ configuration: Optional[Configuration] = None,
834
+ agent_id: Optional[str] = None,
835
+ id: Optional[str] = None,
836
+ input: Optional[str] = None,
837
+ llm_response: Optional[Any] = None,
838
+ tokens: Optional[Tokens] = None,
839
+ is_success: Optional[bool] = True,
840
+ result: Optional[str] = None,
841
+ duration: Optional[float] = 0,
842
+ used_tools: Optional[List[str]] = [],
843
+ ) -> T:
844
+ """
845
+ Synchronously reports an external task to the xpander.ai backend.
846
+
847
+ This function wraps the asynchronous `areport_external_task` method for
848
+ synchronous usage. It submits execution details, including inputs, outputs,
849
+ success status, and resource usage, to the backend.
850
+
851
+ Args:
852
+ configuration (Optional[Configuration]): Optional configuration for API calls.
853
+ agent_id (Optional[str]): Identifier of the agent associated with the task.
854
+ id (Optional[str]): Task identifier (external or internal).
855
+ input (Optional[str]): The input parameters or data used in execution.
856
+ llm_response (Optional[Any]): The raw LLM response or relevant output object.
857
+ tokens (Optional[Tokens]): Tokens used in the execution.
858
+ is_success (Optional[bool]): Whether the task execution was successful. Defaults to True.
859
+ result (Optional[str]): String representation of the final result.
860
+ duration (Optional[float]): Task execution duration, in seconds. Defaults to 0.
861
+ used_tools (Optional[List[str]]): List of tools used during the execution. Defaults to empty list.
862
+
863
+ Returns:
864
+ T: Instance of the Task class, representing the reported task.
865
+
866
+ Example:
867
+ >>> task = Task.report_external_task(
868
+ ... agent_id="agent_xyz",
869
+ ... id="external_task_123",
870
+ ... input="User message",
871
+ ... result="Done"
872
+ ... )
873
+ """
874
+ return run_sync(
875
+ cls.areport_external_task(
876
+ configuration=configuration,
877
+ agent_id=agent_id,
878
+ id=id,
879
+ input=input,
880
+ llm_response=llm_response,
881
+ tokens=tokens,
882
+ is_success=is_success,
883
+ result=result,
884
+ duration=duration,
885
+ used_tools=used_tools,
886
+ )
887
+ )