xpander-sdk 1.60.4__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.4.dist-info → xpander_sdk-2.0.155.dist-info}/WHEEL +1 -1
  84. {xpander_sdk-1.60.4.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.4.jsii.tgz +0 -0
  87. xpander_sdk/py.typed +0 -1
  88. xpander_sdk-1.60.4.dist-info/METADATA +0 -368
  89. xpander_sdk-1.60.4.dist-info/RECORD +0 -9
  90. {xpander_sdk-1.60.4.dist-info → xpander_sdk-2.0.155.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,953 @@
1
+ """
2
+ Agent management and execution models for xpander.ai SDK.
3
+
4
+ This module contains data models and methods for managing and executing
5
+ agents in the xpander.ai Backend-as-a-Service platform.
6
+ """
7
+
8
+ import asyncio
9
+ from datetime import datetime
10
+ import heapq
11
+ import re
12
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
13
+ from httpx import HTTPStatusError
14
+ from loguru import logger
15
+ from pydantic import ConfigDict, computed_field
16
+ from strands import tool as strands_tool
17
+ from xpander_sdk.consts.api_routes import APIRoute
18
+ from xpander_sdk.core.xpander_api_client import APIClient
19
+ from xpander_sdk.exceptions.module_exception import ModuleException
20
+ from xpander_sdk.models.configuration import Configuration
21
+ from xpander_sdk.models.frameworks import AgnoSettings, Framework
22
+ from xpander_sdk.modules.agents.utils.generic import get_db_schema_name
23
+ from xpander_sdk.modules.knowledge_bases.models.knowledge_bases import (
24
+ KnowledgeBaseSearchResult,
25
+ )
26
+ from xpander_sdk.modules.tools_repository.models.mcp import MCPServerDetails
27
+ from xpander_sdk.models.shared import LLMModelT, OutputFormat, ThinkMode, XPanderSharedModel
28
+ from xpander_sdk.models.user import User
29
+ from xpander_sdk.modules.agents.models.agent import (
30
+ AIAgentConnectivityDetailsA2A,
31
+ AIAgentConnectivityDetailsCurl,
32
+ AgentAccessScope,
33
+ AgentDeploymentType,
34
+ AgentGraphItem,
35
+ AgentGraphItemType,
36
+ AgentInstructions,
37
+ AgentOutput,
38
+ AgentSourceNode,
39
+ AgentStatus,
40
+ AgentType,
41
+ DatabaseConnectionString,
42
+ LLMCredentials,
43
+ LLMReasoningEffort,
44
+ )
45
+ from xpander_sdk.modules.agents.models.knowledge_bases import AgentKnowledgeBase
46
+ from xpander_sdk.modules.knowledge_bases.knowledge_bases_module import KnowledgeBases
47
+ from xpander_sdk.modules.knowledge_bases.sub_modules.knowledge_base import KnowledgeBase
48
+ from xpander_sdk.modules.tasks.models.task import AgentExecutionInput
49
+ from xpander_sdk.modules.tasks.sub_modules.task import Task
50
+ from xpander_sdk.modules.tools_repository.models.tool_invocation_result import (
51
+ ToolInvocationResult,
52
+ )
53
+ from xpander_sdk.modules.tools_repository.sub_modules.tool import Tool
54
+ from xpander_sdk.modules.tools_repository.tools_repository_module import ToolsRepository
55
+ from xpander_sdk.modules.tools_repository.utils.schemas import build_model_from_schema
56
+ from xpander_sdk.utils.event_loop import run_sync
57
+ from xpander_sdk.utils.tools import get_openai_agents_sdk_tools
58
+
59
+
60
+ class AgentGraph(XPanderSharedModel):
61
+ """
62
+ Model representing the graph structure of an agent's execution flow.
63
+
64
+ Attributes:
65
+ items (List[AgentGraphItem]): List of all items in the agent's execution graph.
66
+
67
+ Methods:
68
+ __init__: Initialize with a list of graph items.
69
+ get_graph_item: Retrieve a specific graph item by attribute.
70
+ """
71
+
72
+ items: List[AgentGraphItem] = []
73
+
74
+ def __init__(self, graph: list[AgentGraphItem]):
75
+ """
76
+ Initialize the agent graph with provided graph items.
77
+
78
+ Args:
79
+ graph (list[AgentGraphItem]): List of graph item definitions.
80
+ """
81
+ super().__init__()
82
+ self.items = [AgentGraphItem(**item) for item in graph]
83
+
84
+ def get_graph_item(self, attr: str, value: str):
85
+ """
86
+ Retrieve a specific item from the agent's graph based on a matching attribute.
87
+
88
+ Args:
89
+ attr (str): Attribute name to match.
90
+ value (str): Value of the attribute to find.
91
+
92
+ Returns:
93
+ Optional[AgentGraphItem]: The graph item if found, otherwise None.
94
+ """
95
+ for gi in self.items:
96
+ if getattr(gi, attr, None) == value:
97
+ return gi
98
+ return None
99
+
100
+ @computed_field
101
+ @property
102
+ def sub_agents(self) -> List[str]:
103
+ """
104
+ Retrieve the list of sub-agent IDs associated with this agent.
105
+
106
+ This property returns the IDs of all agents that are nested under the current agent.
107
+ A sub-agent is identified when the `type` of the graph item matches `AgentGraphItemType.AGENT`.
108
+
109
+ Returns:
110
+ List[str]:
111
+ A list of unique string IDs representing the agents nested under this agent.
112
+ """
113
+ return [
114
+ gi.item_id
115
+ for gi in self.items
116
+ if gi.type == AgentGraphItemType.AGENT
117
+ ]
118
+
119
+
120
+
121
+ T = TypeVar("T", bound="Agent")
122
+
123
+
124
+ class Agent(XPanderSharedModel):
125
+ """
126
+ Main class for managing agent configuration and execution in xpander.ai.
127
+
128
+ This class captures the complete state and configuration of an agent within
129
+ the xpander.ai platform, including handling task creation, tool invocation,
130
+ and knowledge base interactions.
131
+
132
+ Attributes:
133
+ configuration: Optional[Configuration]
134
+ id: str
135
+ organization_id: str
136
+ name: str
137
+ description: Optional[str]
138
+ unique_name: str
139
+ origin_template: Optional[str]
140
+ environment_id: str
141
+ tools: Optional[ToolsRepository]
142
+ icon: Optional[str]
143
+ source_nodes: Optional[List[AgentSourceNode]]
144
+ access_scope: Optional[AgentAccessScope]
145
+ instructions: Optional[AgentInstructions]
146
+ framework: Framework
147
+ graph: Optional[AgentGraph]
148
+ status: Optional[AgentStatus]
149
+ knowledge_bases: Optional[List[AgentKnowledgeBase]]
150
+ version: Optional[int]
151
+ created_by: Optional[str]
152
+ using_nemo: Optional[bool]
153
+ model_provider: str
154
+ model_name: str
155
+ llm_reasoning_effort: Optional[LLMReasoningEffort] = LLMReasoningEffort.Medium
156
+ llm_api_base: Optional[str]
157
+ webhook_url: Optional[str]
158
+ created_at: Optional[datetime]
159
+ type: Optional[AgentType]
160
+ connectivity_details: Optional[Union[AIAgentConnectivityDetailsA2A,AIAgentConnectivityDetailsCurl, Dict]] = {}
161
+ output_format: Optional[OutputFormat]
162
+ output_schema: Optional[Dict]
163
+ llm_credentials: Optional[LLMCredentials]
164
+ expected_output: Optional[str]
165
+ agno_settings: Optional[AgnoSettings]
166
+
167
+ Example:
168
+ >>> agent = Agent(id="agent123", name="Example Agent")
169
+ """
170
+
171
+ configuration: Optional[Configuration] = None
172
+ id: str
173
+ organization_id: str
174
+ name: str
175
+ description: Optional[str] = None
176
+ unique_name: str
177
+ origin_template: Optional[str] = None
178
+ environment_id: str = None
179
+ tools: Optional[ToolsRepository] = None
180
+ icon: Optional[str] = "🚀"
181
+ connectivity_details: Optional[Union[AIAgentConnectivityDetailsA2A,AIAgentConnectivityDetailsCurl, Dict]] = {}
182
+ deployment_type: Optional[AgentDeploymentType] = AgentDeploymentType.Serverless
183
+ source_nodes: Optional[List[AgentSourceNode]] = []
184
+ access_scope: Optional[AgentAccessScope] = AgentAccessScope.Organizational
185
+ instructions: Optional[AgentInstructions] = AgentInstructions(
186
+ role=[], goal=[], general=""
187
+ )
188
+ framework: Framework # agents framework
189
+ graph: Optional[AgentGraph] = None
190
+ status: Optional[AgentStatus] = AgentStatus.ACTIVE
191
+ knowledge_bases: Optional[List[AgentKnowledgeBase]] = []
192
+ version: Optional[int] = 1
193
+ created_by: Optional[str] = None
194
+ using_nemo: Optional[bool] = False
195
+ model_provider: str
196
+ model_name: str
197
+ llm_reasoning_effort: Optional[LLMReasoningEffort] = LLMReasoningEffort.Medium
198
+ llm_api_base: Optional[str] = None
199
+ webhook_url: Optional[str] = None
200
+ created_at: Optional[datetime] = None
201
+ type: Optional[AgentType] = None
202
+ output_format: Optional[OutputFormat] = OutputFormat.Markdown
203
+ output_schema: Optional[Dict] = None
204
+
205
+ llm_credentials: Optional[LLMCredentials] = None
206
+ expected_output: Optional[str] = ""
207
+ agno_settings: Optional[AgnoSettings] = AgnoSettings()
208
+
209
+ _connection_string: Optional[DatabaseConnectionString] = None
210
+
211
+ model_config = ConfigDict(arbitrary_types_allowed=True)
212
+
213
+ def model_post_init(self, context):
214
+ """
215
+ Post-initialization hook for the model.
216
+
217
+ This method sets the current agent in the global state and then calls the
218
+ parent class's `model_post_init` method.
219
+
220
+ Parameters:
221
+ context (Any): Context object provided during model initialization.
222
+
223
+ Returns:
224
+ Any: The result from the superclass `model_post_init` method.
225
+
226
+ Note:
227
+ This method uses `self.configuration.state.agent = self` to register the current agent
228
+ in the global state.
229
+
230
+ Powered by xpander.ai
231
+ """
232
+ self.configuration.state.agent = self
233
+ return super().model_post_init(context)
234
+
235
+ @computed_field
236
+ @property
237
+ def mcp_servers(self) -> List[MCPServerDetails]:
238
+ """
239
+ List MCP server details configured in the agent's graph.
240
+
241
+ Returns:
242
+ List[MCPServerDetails]: Details of MCP servers configured for this agent.
243
+ """
244
+ return [
245
+ gi.settings.mcp_settings
246
+ for gi in self.graph.items
247
+ if gi.type == AgentGraphItemType.MCP
248
+ ]
249
+
250
+ @computed_field
251
+ @property
252
+ def output(self) -> AgentOutput:
253
+ """
254
+ Construct the output settings for this agent.
255
+
256
+ Returns:
257
+ AgentOutput: Output configuration based on schema and format.
258
+ """
259
+ return AgentOutput(
260
+ output_schema=(
261
+ build_model_from_schema(
262
+ model_name="StructuredOutput", schema=self.output_schema
263
+ )
264
+ if self.output_schema and self.output_format == OutputFormat.Json
265
+ else None
266
+ ),
267
+ is_markdown=self.output_format == OutputFormat.Markdown,
268
+ use_json_mode=self.output_format == OutputFormat.Json,
269
+ )
270
+
271
+ @classmethod
272
+ async def aload(
273
+ cls: Type[T],
274
+ agent_id: str,
275
+ configuration: Optional[Configuration] = None,
276
+ version: Optional[int] = None,
277
+ ) -> T:
278
+ """
279
+ Asynchronously load an agent's configuration and settings by ID.
280
+
281
+ Args:
282
+ agent_id (str): Unique identifier of the agent.
283
+ configuration (Optional[Configuration]): SDK configuration to use.
284
+ version (Optional[int]): Specific agent version to load.
285
+
286
+ Returns:
287
+ T: Loaded agent object.
288
+
289
+ Example:
290
+ >>> agent = await Agent.aload(agent_id="agent123")
291
+ """
292
+ try:
293
+ client = APIClient(configuration=configuration)
294
+ headers = {}
295
+ if version:
296
+ headers["x-agent-version"] = str(version)
297
+
298
+ response_data: dict = await client.make_request(
299
+ path=APIRoute.GetAgent.format(agent_id=agent_id), headers=headers
300
+ )
301
+ agent = cls.model_validate({**response_data, "graph": None, "tools": None, "configuration": configuration or Configuration()})
302
+ agent.graph = AgentGraph(response_data.get("graph", []))
303
+ agent.tools = ToolsRepository(
304
+ configuration=agent.configuration, tools=response_data.get("tools", []), agent_graph=agent.graph
305
+ )
306
+
307
+ if agent.tools.should_sync_local_tools():
308
+ asyncio.create_task(
309
+ agent.sync_local_tools(tools=agent.tools.get_local_tools_for_sync())
310
+ )
311
+
312
+ return agent
313
+ except HTTPStatusError as e:
314
+ raise ModuleException(
315
+ status_code=e.response.status_code, description=e.response.text
316
+ )
317
+ except Exception as e:
318
+ raise ModuleException(
319
+ status_code=500, description=f"Failed to load agent - {str(e)}"
320
+ )
321
+
322
+ @classmethod
323
+ def load(
324
+ cls: Type[T],
325
+ agent_id: str,
326
+ configuration: Optional[Configuration] = None,
327
+ version: Optional[int] = None,
328
+ ) -> T:
329
+ """
330
+ Synchronously load an agent's configuration using its unique ID.
331
+
332
+ Args:
333
+ agent_id (str): Unique identifier of the agent.
334
+ configuration (Optional[Configuration]): SDK configuration to use.
335
+ version (Optional[int]): Specific agent version to load.
336
+
337
+ Returns:
338
+ T: Loaded agent object.
339
+
340
+ Example:
341
+ >>> agent = Agent.load(agent_id="agent123")
342
+ """
343
+ return run_sync(
344
+ cls.aload(agent_id=agent_id, configuration=configuration, version=version)
345
+ )
346
+
347
+ async def aget_connection_string(self):
348
+ """
349
+ Asynchronously retrieve the agent's connection string from the platform.
350
+
351
+ Returns:
352
+ DatabaseConnectionString: The connection string details.
353
+ """
354
+ try:
355
+ if self._connection_string:
356
+ return self._connection_string
357
+
358
+ client = APIClient(configuration=self.configuration)
359
+ connection_string = await client.make_request(
360
+ path=APIRoute.GetAgentConnectionString.format(agent_id=self.id)
361
+ )
362
+ self._connection_string = DatabaseConnectionString(**connection_string)
363
+ return self._connection_string
364
+ except HTTPStatusError as e:
365
+ raise ModuleException(
366
+ status_code=e.response.status_code, description=e.response.text
367
+ )
368
+ except Exception as e:
369
+ raise ModuleException(
370
+ status_code=500,
371
+ description=f"Failed to get agent connection string - {str(e)}",
372
+ )
373
+
374
+ def get_connection_string(self):
375
+ """
376
+ Synchronously retrieve the agent's connection string from the platform.
377
+
378
+ Returns:
379
+ DatabaseConnectionString: The connection string details.
380
+
381
+ Example:
382
+ >>> connection_string = agent.get_connection_string()
383
+ """
384
+ return run_sync(self.aget_connection_string())
385
+
386
+ async def acreate_task(
387
+ self,
388
+ prompt: Optional[str] = "",
389
+ existing_task_id: Optional[str] = None,
390
+ file_urls: Optional[List[str]] = [],
391
+ user_details: Optional[User] = None,
392
+ agent_version: Optional[str] = None,
393
+ tool_call_payload_extension: Optional[dict] = None,
394
+ source: Optional[str] = "sdk",
395
+ worker_id: Optional[str] = None,
396
+ run_locally: Optional[bool] = False,
397
+ output_format: Optional[OutputFormat] = None,
398
+ output_schema: Optional[Dict] = None,
399
+ events_streaming: Optional[bool] = False,
400
+ additional_context: Optional[str] = None,
401
+ expected_output: Optional[str] = None,
402
+ mcp_servers: Optional[List[MCPServerDetails]] = [],
403
+ triggering_agent_id: Optional[str] = None,
404
+ title: Optional[str] = None,
405
+ think_mode: Optional[ThinkMode] = ThinkMode.Default,
406
+ disable_attachment_injection: Optional[bool] = False,
407
+ ) -> Task:
408
+ """
409
+ Asynchronously create a new task and link it to this agent.
410
+
411
+ Args:
412
+ prompt (Optional[str]): Task initiation prompt.
413
+ existing_task_id (Optional[str]): Existing task id if exists.
414
+ file_urls (Optional[List[str]]): URLs of files related to the task.
415
+ user_details (Optional[User]): User linked to this task context.
416
+ agent_version (Optional[str]): Optional agent version to use.
417
+ tool_call_payload_extension (Optional[dict]): Extend payload with additional information.
418
+ source (Optional[str]): Origin or source of the request. default = "sdk".
419
+ worker_id (Optional[str]): Worker identifier if applicable.
420
+ run_locally (Optional[bool]): Indicates if task should run locally.
421
+ output_format (Optional[OutputFormat]): Format for output response.
422
+ output_schema (Optional[Dict]): Schema defining structure of output.
423
+ events_streaming (Optional[bool]): Flag idicating for events are required for this task.
424
+ additional_context (Optional[str]): Additional context to be passed to the agent.
425
+ expected_output (Optional[str]): Expected output of the execution.
426
+ mcp_servers (Optional[List[MCPServerDetails]]): Optional list of mcp servers to use.
427
+ triggering_agent_id (Optional[str]): Optional triggering agent id.
428
+ title (Optional[str]): Optional task title.
429
+ think_mode (Optional[ThinkMode]): Optional task think mode, defaults to "default".
430
+ disable_attachment_injection (Optional[bool]): Optional selection if to disable attachment injection to the context window.
431
+
432
+ Returns:
433
+ Task: Created Task object linked to this agent.
434
+ """
435
+ try:
436
+
437
+ headers = {}
438
+ if agent_version:
439
+ headers['x-agent-version'] = str(agent_version)
440
+
441
+ client = APIClient(configuration=self.configuration)
442
+ created_task = await client.make_request(
443
+ path=APIRoute.TaskCrud.format(agent_or_task_id=self.id),
444
+ method="POST",
445
+ headers=headers,
446
+ payload={
447
+ "id": existing_task_id,
448
+ "input": AgentExecutionInput(
449
+ text=prompt, files=file_urls, user=user_details
450
+ ).model_dump(),
451
+ "payload_extension": tool_call_payload_extension,
452
+ "source": source,
453
+ "worker_id": worker_id,
454
+ "output_format": output_format,
455
+ "output_schema": output_schema,
456
+ "run_locally": run_locally,
457
+ "events_streaming": events_streaming,
458
+ "additional_context": additional_context,
459
+ "expected_output": expected_output,
460
+ "mcp_servers": [server.model_dump() for server in mcp_servers],
461
+ "triggering_agent_id": triggering_agent_id,
462
+ "title": title,
463
+ "think_mode": think_mode.value,
464
+ "disable_attachment_injection": disable_attachment_injection,
465
+ },
466
+ )
467
+ return Task(**created_task, configuration=self.configuration)
468
+ except Exception as e:
469
+ if isinstance(e, HTTPStatusError):
470
+ raise ModuleException(e.response.status_code, e.response.text)
471
+ raise ModuleException(500, f"Failed to create task - {str(e)}")
472
+
473
+ def create_task(self, *args, **kwargs) -> Task:
474
+ """
475
+ Synchronously create a new task for this agent.
476
+
477
+ Args:
478
+ *args, **kwargs: Arguments matching acreate_task.
479
+
480
+ Returns:
481
+ Task: Created Task object linked to this agent.
482
+
483
+ Example:
484
+ >>> task = agent.create_task(prompt="Analyze data files")
485
+ """
486
+ return run_sync(self.acreate_task(*args, **kwargs))
487
+
488
+ async def ainvoke_tool(
489
+ self,
490
+ tool: Tool,
491
+ payload: Any,
492
+ payload_extension: Optional[Dict] = {},
493
+ task_id: Optional[str] = None,
494
+ tool_call_id: Optional[str] = None,
495
+ ) -> ToolInvocationResult:
496
+ """
497
+ Asynchronously invoke a specific tool linked to the agent.
498
+
499
+ Args:
500
+ tool (Tool): Tool to be invoked during the execution.
501
+ payload (Any): Data payload to be passed to the tool.
502
+ payload_extension (Optional[Dict]): Optional payload extensions.
503
+ task_id (Optional[str]): Related task ID if linked.
504
+ tool_call_id (Optional[str]): Optional tool call identifier.
505
+
506
+ Returns:
507
+ ToolInvocationResult: Result object with execution details.
508
+ """
509
+ return await tool.ainvoke(
510
+ agent_id=self.id,
511
+ payload=payload,
512
+ agent_version=self.version,
513
+ payload_extension=payload_extension,
514
+ task_id=task_id,
515
+ tool_call_id=tool_call_id,
516
+ )
517
+
518
+ def invoke_tool(self, *args, **kwargs) -> ToolInvocationResult:
519
+ """
520
+ Synchronously invoke a specific tool linked to the agent.
521
+
522
+ Args:
523
+ *args, **kwargs: Arguments matching ainvoke_tool.
524
+
525
+ Returns:
526
+ ToolInvocationResult: Result object with execution details.
527
+
528
+ Example:
529
+ >>> result = agent.invoke_tool(my_tool, payload={"data": "value"})
530
+ """
531
+ return run_sync(self.ainvoke_tool(*args, **kwargs))
532
+
533
+ async def aget_knowledge_bases(self) -> List[KnowledgeBase]:
534
+ """
535
+ Asynchronously retrieve all linked knowledge bases for this agent.
536
+
537
+ Returns:
538
+ List[KnowledgeBase]: List of linked knowledge bases.
539
+
540
+ Example:
541
+ >>> knowledge_bases = await agent.aget_knowledge_bases()
542
+ """
543
+ kb_modules = KnowledgeBases(configuration=self.configuration)
544
+ tasks = [
545
+ kb_modules.aget(knowledge_base_id=kb.id) for kb in self.knowledge_bases
546
+ ]
547
+ return await asyncio.gather(*tasks)
548
+
549
+ def get_knowledge_bases(self) -> List[KnowledgeBase]:
550
+ """
551
+ Synchronously retrieve all linked knowledge bases for this agent.
552
+
553
+ Returns:
554
+ List[KnowledgeBase]: List of linked knowledge bases.
555
+
556
+ Example:
557
+ >>> knowledge_bases = agent.get_knowledge_bases()
558
+ """
559
+ return run_sync(self.aget_knowledge_bases())
560
+
561
+ async def sync_local_tools(self, tools: List[Tool]):
562
+ """
563
+ Asynchronously sync local tools with the backend graph representation.
564
+
565
+ Args:
566
+ tools (List[Tool]): List of local tools to sync.
567
+ """
568
+ try:
569
+ client = APIClient(configuration=self.configuration)
570
+ response_data: dict = await client.make_request(
571
+ path=APIRoute.SyncLocalTools.format(agent_id=self.id),
572
+ method="PATCH",
573
+ payload=[
574
+ tool.model_dump(exclude={"fn", "schema", "configuration"})
575
+ for tool in tools
576
+ ],
577
+ )
578
+ self.graph = AgentGraph(response_data.get("graph", []))
579
+
580
+ # set all local tools as synced
581
+ for tool in tools:
582
+ tool.is_synced = True
583
+ except Exception as e:
584
+ logger.warning(f"Failed to sync local tools - {str(e)}")
585
+
586
+ async def aget_db(self, async_db: Optional[bool] = True):
587
+ """
588
+ Asynchronously retrieve the db for this agent.
589
+
590
+ Args:
591
+ async_db (Optional[bool]): Should return async database client.
592
+
593
+ Returns:
594
+ AsyncPostgresDb|PostgresDb: Initialized (async?) db (Agno PG) for agent sessions.
595
+
596
+ Raises:
597
+ NotImplementedError: If the framework does not support storage.
598
+ ImportError: If required dependencies are missing.
599
+ ValueError: If the connection string for storage is invalid.
600
+ """
601
+ framework = Framework.Agno # will be removed
602
+ if self.framework != framework:
603
+ raise NotImplementedError(
604
+ f"Storage for framework '{self.framework}' is not supported."
605
+ )
606
+
607
+ if not self.agno_settings.session_storage:
608
+ raise LookupError("Session storage is not enabled for this agent.")
609
+
610
+ try:
611
+ from agno.db.postgres import AsyncPostgresDb, PostgresDb
612
+ except ImportError as e:
613
+ raise ImportError(
614
+ "The 'agno' extras must be installed to use this db. "
615
+ "Run `pip install xpander-sdk[agno]`."
616
+ ) from e
617
+
618
+ connection_string = await self.aget_connection_string()
619
+ if not connection_string or not connection_string.connection_uri.uri:
620
+ raise ValueError(
621
+ "Invalid connection string provided for Agno db."
622
+ )
623
+
624
+ schema = get_db_schema_name(agent_id=self.id)
625
+
626
+ client_type = AsyncPostgresDb if async_db else PostgresDb
627
+
628
+ return client_type(
629
+ db_schema=schema,
630
+ db_url=connection_string.connection_uri.uri.replace("postgresql", "postgresql+psycopg"+("_async" if async_db else "")),
631
+ )
632
+
633
+ def get_db(self) -> Any:
634
+ """
635
+ Synchronously retrieve the db for this agent.
636
+
637
+ Returns:
638
+ Any: Initialized db for agent sessions.
639
+
640
+ Example:
641
+ >>> db = agent.get_db()
642
+ """
643
+ return run_sync(self.aget_db(async_db=False))
644
+
645
+ @computed_field
646
+ @property
647
+ def search_knowledge(self) -> bool:
648
+ """
649
+ Check if any knowledge bases are linked to this agent.
650
+
651
+ Returns:
652
+ bool: True if one or more knowledge bases are linked, otherwise False.
653
+ """
654
+ return len(self.knowledge_bases) != 0
655
+
656
+ @computed_field
657
+ @property
658
+ def is_a_team(self) -> bool:
659
+ """
660
+ Check if this agent run in Team mode
661
+
662
+ Returns:
663
+ bool: True if has sub agents, otherwise False.
664
+ """
665
+ return self.graph.sub_agents and len(self.graph.sub_agents) != 0
666
+
667
+ def knowledge_bases_retriever(
668
+ self,
669
+ ) -> Callable[[str, Optional[Any], int], asyncio.Future]:
670
+ """
671
+ Retrieve callable to perform search within linked knowledge bases.
672
+
673
+ Returns:
674
+ Callable[[str, Optional[Any], int], asyncio.Future]: Function to execute a search query.
675
+ """
676
+
677
+ def search(
678
+ query: str, agent: Optional[Any] = None, num_documents: int = 5, **kwargs
679
+ ) -> Optional[List[dict]]:
680
+ """
681
+ Perform search across all linked knowledge bases for a query.
682
+
683
+ Args:
684
+ query (str): Search query string.
685
+ agent (Optional[Any]): Optional agent context to refine query.
686
+ num_documents (int): Number of top documents to retrieve.
687
+
688
+ Returns:
689
+ Optional[List[dict]]: Top matching documents from knowledge bases.
690
+ """
691
+ try:
692
+ num_documents = num_documents or 10
693
+ all_results: List[KnowledgeBaseSearchResult] = []
694
+ # Retrieve all the agent's knowledge bases
695
+ agents_kbs = self.get_knowledge_bases()
696
+
697
+ # Launch concurrent searches
698
+ for kb in agents_kbs:
699
+ all_results.extend(
700
+ kb.search(search_query=query, top_k=num_documents)
701
+ )
702
+
703
+ # Sort by score descending and return top N
704
+ sorted_results = heapq.nlargest(
705
+ num_documents, all_results, key=lambda x: x.score
706
+ )
707
+
708
+ return [result.model_dump() for result in sorted_results]
709
+
710
+ except Exception as e:
711
+ logger.error(f"Error during vector database search: {str(e)}")
712
+ return []
713
+
714
+ return search
715
+
716
+ @computed_field
717
+ @property
718
+ def is_active(self) -> bool:
719
+ """
720
+ Check if the agent is active.
721
+
722
+ Returns:
723
+ bool: True if the agent is active, False if not.
724
+ """
725
+ return self.status == AgentStatus.ACTIVE
726
+
727
+ async def aget_user_sessions(self, user_id: str):
728
+ """
729
+ Asynchronously retrieve all user sessions associated with this agent.
730
+
731
+ This method loads all saved session records linked to the specified user ID from
732
+ the agent's db. It is only supported for agents using the Agno framework
733
+ with session storage enabled.
734
+
735
+ Args:
736
+ user_id (str): Identifier of the user whose sessions are to be retrieved.
737
+
738
+ Returns:
739
+ Any: A list of session records associated with the user.
740
+
741
+ Raises:
742
+ NotImplementedError: If the agent framework does not support session storage.
743
+ LookupError: If session storage is not enabled for this agent.
744
+ ImportError: If required dependencies for Agno storage are not installed.
745
+ ValueError: If the agent connection string is invalid.
746
+
747
+ Example:
748
+ >>> sessions = await agent.aget_user_sessions(user_id="user_123")
749
+ """
750
+ db = await self.aget_db()
751
+ from agno.db import SessionType
752
+ sessions = await asyncio.to_thread(db.get_sessions, user_id=user_id, limit=50, session_type = SessionType.TEAM if self.is_a_team else SessionType.AGENT)
753
+ return sessions
754
+
755
+ def get_user_sessions(self, user_id: str):
756
+ """
757
+ Synchronously retrieve all user sessions associated with this agent.
758
+
759
+ This method wraps the asynchronous `aget_user_sessions` method and returns the result
760
+ in a synchronous context. It loads session data for a given user ID from the agent's db.
761
+
762
+ Args:
763
+ user_id (str): Identifier of the user whose sessions are to be retrieved.
764
+
765
+ Returns:
766
+ Any: A list of sessions related to the given user.
767
+
768
+ Example:
769
+ >>> sessions = agent.get_user_sessions(user_id="user_123")
770
+ """
771
+ return run_sync(self.aget_user_sessions(user_id=user_id))
772
+
773
+ async def aget_session(self, session_id: str):
774
+ """
775
+ Asynchronously retrieve a single session by its session ID.
776
+
777
+ This method accesses the agent's db and loads the session record
778
+ corresponding to the given session ID. It is only supported for agents using
779
+ the Agno framework with session storage enabled.
780
+
781
+ Args:
782
+ session_id (str): Unique identifier of the session to retrieve.
783
+
784
+ Returns:
785
+ Any: A single session record if found, or None if the session does not exist.
786
+
787
+ Raises:
788
+ NotImplementedError: If the agent framework does not support session storage.
789
+ LookupError: If session storage is not enabled for this agent.
790
+ ImportError: If required dependencies for Agno storage are not installed.
791
+ ValueError: If the agent connection string is invalid.
792
+
793
+ Example:
794
+ >>> session = await agent.aget_session(session_id="sess_456")
795
+ """
796
+ db = await self.aget_db()
797
+ from agno.db import SessionType
798
+ session = await asyncio.to_thread(db.get_session, session_id=session_id, session_type = SessionType.TEAM if self.is_a_team else SessionType.AGENT)
799
+ return await session
800
+
801
+ def get_session(self, session_id: str):
802
+ """
803
+ Synchronously retrieve a single session by its session ID.
804
+
805
+ This method wraps the asynchronous `aget_session` and returns the result
806
+ in a synchronous context. It retrieves the session record from the agent's
807
+ db using the given session ID.
808
+
809
+ Args:
810
+ session_id (str): Unique identifier of the session to retrieve.
811
+
812
+ Returns:
813
+ Any: A single session record if found, or None if the session does not exist.
814
+
815
+ Example:
816
+ >>> session = agent.get_session(session_id="sess_456")
817
+ """
818
+ return run_sync(self.aget_session(session_id=session_id))
819
+
820
+ async def adelete_session(self, session_id: str):
821
+ """
822
+ Asynchronously delete a session by its session ID.
823
+
824
+ This method removes a specific session record from the agent's db
825
+ based on the provided session ID. It is only supported for agents using the
826
+ Agno framework with session storage enabled.
827
+
828
+ Args:
829
+ session_id (str): Unique identifier of the session to delete.
830
+
831
+ Raises:
832
+ NotImplementedError: If the agent framework does not support session storage.
833
+ LookupError: If session storage is not enabled for this agent.
834
+ ImportError: If required dependencies for Agno storage are not installed.
835
+ ValueError: If the agent connection string is invalid.
836
+
837
+ Example:
838
+ >>> await agent.adelete_session(session_id="sess_456")
839
+ """
840
+ db = await self.aget_db(async_db=False)
841
+ await asyncio.to_thread(db.delete_session, session_id=session_id)
842
+
843
+ def delete_session(self, session_id: str):
844
+ """
845
+ Synchronously delete a session by its session ID.
846
+
847
+ This method wraps the asynchronous `adelete_session` and removes the session
848
+ record from the agent's db in a synchronous context.
849
+
850
+ Args:
851
+ session_id (str): Unique identifier of the session to delete.
852
+
853
+ Example:
854
+ >>> agent.delete_session(session_id="sess_456")
855
+ """
856
+ return run_sync(self.adelete_session(session_id=session_id))
857
+
858
+ def attach_knowledge_base(
859
+ self,
860
+ knowledge_base: Optional[KnowledgeBase] = None,
861
+ knowledge_base_id: Optional[str] = None
862
+ ) -> None:
863
+ """
864
+ Attach a knowledge base to the agent if it is not already linked.
865
+
866
+ This method ensures that a knowledge base is associated with the agent, either
867
+ via a `KnowledgeBase` instance or a raw ID. It avoids duplicate links by checking
868
+ for existing associations.
869
+
870
+ Args:
871
+ knowledge_base (Optional[KnowledgeBase]): The KnowledgeBase instance to attach.
872
+ knowledge_base_id (Optional[str]): The unique identifier of the knowledge base.
873
+
874
+ Raises:
875
+ ValueError: If neither a knowledge base nor an ID is provided.
876
+ TypeError: If a provided knowledge base is not a valid `KnowledgeBase` instance.
877
+
878
+ Example:
879
+ >>> agent.attach_knowledge_base(knowledge_base_id="kb_12345")
880
+ >>> agent.attach_knowledge_base(knowledge_base=my_kb_instance)
881
+
882
+ Note:
883
+ This change only affects the runtime instance of the agent.
884
+ To persist changes, an explicit save or sync must be called.
885
+ """
886
+ if not knowledge_base and not knowledge_base_id:
887
+ raise ValueError("You must provide either a knowledge_base instance or a knowledge_base_id.")
888
+
889
+ if knowledge_base:
890
+ if not isinstance(knowledge_base, KnowledgeBase):
891
+ raise TypeError("Expected 'knowledge_base' to be an instance of KnowledgeBase.")
892
+ knowledge_base_id = knowledge_base.id
893
+
894
+ if not any(kb.id == knowledge_base_id for kb in self.knowledge_bases):
895
+ self.knowledge_bases.append(AgentKnowledgeBase(id=knowledge_base_id))
896
+
897
+ @computed_field
898
+ @property
899
+ def strands_tools(self) -> List[Any]:
900
+ tools = []
901
+ for _tool in self.tools.list:
902
+ def make_tool(_tool_def: Tool):
903
+ async def invoke(payload: dict):
904
+ return await _tool_def.ainvoke(
905
+ task_id=self.configuration.state.task.id
906
+ if self.configuration.state.task
907
+ else None,
908
+ agent_id=self.id,
909
+ agent_version=self.version,
910
+ payload=payload,
911
+ configuration=_tool.configuration
912
+ )
913
+ return invoke
914
+
915
+ tools.append(
916
+ strands_tool(
917
+ func=make_tool(_tool),
918
+ name=_tool.id,
919
+ description=_tool.description,
920
+ inputSchema={
921
+ "json": {
922
+ "type": "object",
923
+ "properties":{
924
+ "payload": _tool.parameters
925
+ },
926
+ "required": ["payload"]
927
+ }
928
+ }
929
+ )
930
+ )
931
+
932
+ return tools
933
+
934
+ @computed_field
935
+ @property
936
+ def sanitized_name(self) -> str:
937
+ # Replace invalid characters with underscores
938
+ sanitized = re.sub(r'[^A-Za-z0-9_]', '_', self.name)
939
+
940
+ # If the first character is invalid, prefix with underscore
941
+ if not re.match(r'[A-Za-z_]', sanitized):
942
+ sanitized = '_' + sanitized
943
+
944
+ # Prevent empty result
945
+ if not sanitized:
946
+ sanitized = '_agent'
947
+
948
+ return sanitized
949
+
950
+ @computed_field
951
+ @property
952
+ def openai_agents_sdk_tools(self) -> List[Any]:
953
+ return get_openai_agents_sdk_tools(self)