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.
@@ -35,6 +35,7 @@ and deleting resources.
35
35
  ListTasks = "/agent-execution/executions/history/{agent_id}"
36
36
  ListUserTasks = "/agent-execution/executions/history/user/{user_id}"
37
37
  GetTask = "/agent-execution/{task_id}/status"
38
+ GetTaskActivityLog = "/activity/{agent_id}/{task_id}"
38
39
  TaskCrud = "/agent-execution/{agent_or_task_id}"
39
40
  UpdateTask = "/agent-execution/{task_id}/update"
40
41
  ReportExternalTask = "/agent-execution/{agent_id}/report_task"
@@ -0,0 +1,65 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import Any, List, Literal, Optional, Union
4
+
5
+ from xpander_sdk.models.events import ToolCallRequestReasoning
6
+ from xpander_sdk.models.shared import XPanderSharedModel
7
+ from xpander_sdk.models.user import User
8
+ from xpander_sdk.modules.tools_repository.models.mcp import MCPOAuthGetTokenResponse
9
+
10
+ class AgentActivityThreadMessageContent(XPanderSharedModel):
11
+ text: Optional[str] = None
12
+ files: Optional[List[str]] = []
13
+
14
+ class AgentActivityThreadMessage(XPanderSharedModel):
15
+ id: str
16
+ created_at: datetime
17
+ role: Literal["user","agent"]
18
+ content: AgentActivityThreadMessageContent
19
+
20
+ class AgentActivityThreadToolCall(XPanderSharedModel):
21
+ id: str
22
+ created_at: datetime
23
+ tool_name: str
24
+ payload: Any
25
+ is_error: Optional[bool] = False
26
+ reasoning: Optional[ToolCallRequestReasoning] = None
27
+ result: Optional[Any] = None
28
+
29
+ class AgentActivityThreadReasoningType(str, Enum):
30
+ Think = "think"
31
+ Analyze = "analyze"
32
+
33
+ class AgentActivityThreadReasoning(XPanderSharedModel):
34
+ id: str
35
+ created_at: datetime
36
+ type: AgentActivityThreadReasoningType
37
+ title: str
38
+ confidence: float
39
+ thought: Optional[str] = None
40
+ action: Optional[str] = None
41
+ result: Optional[str] = None
42
+ analysis: Optional[str] = None
43
+
44
+ class AgentActivityThreadSubAgentTrigger(XPanderSharedModel):
45
+ id: str
46
+ created_at: datetime
47
+ agent_id: str
48
+ query: Optional[str] = None
49
+ files: Optional[List[str]] = []
50
+ reasoning: ToolCallRequestReasoning
51
+
52
+ class AgentActivityThreadAuth(MCPOAuthGetTokenResponse):
53
+ id: str
54
+ created_at: datetime
55
+
56
+ AgentActivityThreadMessageType = Union[AgentActivityThreadMessage, AgentActivityThreadToolCall, AgentActivityThreadReasoning, AgentActivityThreadSubAgentTrigger, AgentActivityThreadAuth]
57
+ class AgentActivityThread(XPanderSharedModel):
58
+ id: str
59
+ created_at: datetime
60
+ messages: List[AgentActivityThreadMessageType]
61
+ user: Optional[User] = None
62
+
63
+ class AgentActivityThreadListItem(XPanderSharedModel):
64
+ id: str
65
+ created_at: datetime
@@ -0,0 +1,18 @@
1
+ from typing import List, Optional
2
+ from .shared import XPanderSharedModel
3
+
4
+ class DeepPlanningItem(XPanderSharedModel):
5
+ id: str
6
+ title: str
7
+ completed: Optional[bool] = False
8
+
9
+ class DeepPlanning(XPanderSharedModel):
10
+ enabled: Optional[bool] = False
11
+ enforce: Optional[bool] = False
12
+ started: Optional[bool] = False
13
+ question_raised: Optional[bool] = False
14
+ tasks: Optional[List[DeepPlanningItem]] = []
15
+
16
+ class PlanFollowingStatus(XPanderSharedModel):
17
+ can_finish: bool
18
+ uncompleted_tasks: Optional[List[DeepPlanningItem]] = []
@@ -68,3 +68,6 @@ class TaskUpdateEventType(str, Enum):
68
68
  # reasoning
69
69
  Think = "think"
70
70
  Analyze = "analyze"
71
+
72
+ # deep planning
73
+ PlanUpdated = "plan_updated"
@@ -383,7 +383,10 @@ class AgentGraphItem(BaseModel):
383
383
  llm_settings: Optional[List[AgentGraphItemLLMSettings]] = []
384
384
  is_first: Optional[bool] = False
385
385
 
386
-
386
+ class LLMReasoningEffort(str, Enum):
387
+ Low = "low"
388
+ Medium = "medium"
389
+ High = "high"
387
390
 
388
391
  class AIAgentConnectivityDetailsA2AAuthType(str, Enum):
389
392
  NoAuth = "none"
@@ -40,6 +40,7 @@ from xpander_sdk.modules.agents.models.agent import (
40
40
  AgentType,
41
41
  DatabaseConnectionString,
42
42
  LLMCredentials,
43
+ LLMReasoningEffort,
43
44
  )
44
45
  from xpander_sdk.modules.agents.models.knowledge_bases import AgentKnowledgeBase
45
46
  from xpander_sdk.modules.knowledge_bases.knowledge_bases_module import KnowledgeBases
@@ -151,6 +152,9 @@ class Agent(XPanderSharedModel):
151
152
  using_nemo: Optional[bool]
152
153
  model_provider: str
153
154
  model_name: str
155
+ llm_reasoning_effort: Optional[LLMReasoningEffort] = LLMReasoningEffort.Medium
156
+ deep_planning: Optional[bool] = False
157
+ enforce_deep_planning: Optional[bool] = False
154
158
  llm_api_base: Optional[str]
155
159
  webhook_url: Optional[str]
156
160
  created_at: Optional[datetime]
@@ -192,6 +196,9 @@ class Agent(XPanderSharedModel):
192
196
  using_nemo: Optional[bool] = False
193
197
  model_provider: str
194
198
  model_name: str
199
+ llm_reasoning_effort: Optional[LLMReasoningEffort] = LLMReasoningEffort.Medium
200
+ deep_planning: Optional[bool] = False
201
+ enforce_deep_planning: Optional[bool] = False
195
202
  llm_api_base: Optional[str] = None
196
203
  webhook_url: Optional[str] = None
197
204
  created_at: Optional[datetime] = None
@@ -8,7 +8,7 @@ from loguru import logger
8
8
  from xpander_sdk import Configuration
9
9
  from xpander_sdk.models.shared import OutputFormat, ThinkMode
10
10
  from xpander_sdk.modules.agents.agents_module import Agents
11
- from xpander_sdk.modules.agents.models.agent import AgentGraphItemType
11
+ from xpander_sdk.modules.agents.models.agent import AgentGraphItemType, LLMReasoningEffort
12
12
  from xpander_sdk.modules.agents.sub_modules.agent import Agent
13
13
  from xpander_sdk.modules.backend.utils.mcp_oauth import authenticate_mcp_server
14
14
  from xpander_sdk.modules.tasks.sub_modules.task import Task
@@ -213,8 +213,244 @@ async def build_agent_args(
213
213
  if args["model"] and args["model"].id and args["model"].id.startswith("gpt-5"):
214
214
  del args["model"].temperature
215
215
 
216
+ # configure deep planning guidance
217
+ _configure_deep_planning_guidance(args=args, agent=xpander_agent, task=task)
216
218
  return args
217
219
 
220
+ def _configure_deep_planning_guidance(args: Dict[str, Any], agent: Agent, task: Optional[Task]) -> None:
221
+ if args and agent and task and agent.deep_planning and task.deep_planning.enabled == True:
222
+ # add instructions guidance
223
+ if not "instructions" in args:
224
+ args["instructions"] = ""
225
+
226
+ args["instructions"] += """\n
227
+ <important_planning_instructions>
228
+ ## **Deep Planning Tools - Essential for Multi-Step Tasks**
229
+
230
+ When handling complex tasks with multiple steps, use these planning tools to track progress systematically.
231
+
232
+ ### **Core Workflow**
233
+ 1. **CREATE** plan at the start (`xpcreate-agent-plan`)
234
+ 2. **START** plan execution (`xpstart-execution-plan`) - MANDATORY to enable enforcement
235
+ 3. **CHECK** plan before each action (`xpget-agent-plan`)
236
+ 4. **COMPLETE** task immediately after finishing it (`xpcomplete-agent-plan-item`)
237
+ 5. **ASK** user for info if needed (`xpask-for-information`)
238
+ 6. Repeat steps 3-5 until all tasks are done
239
+
240
+ ---
241
+
242
+ ### **Tool Reference**
243
+
244
+ #### **1. xpcreate-agent-plan** - Create Initial Plan
245
+ **When to use**: At the very start of any multi-step task, ONLY if no plan exists yet
246
+ **Note**: After creating a plan, you MUST call `xpstart-execution-plan` to begin enforcement
247
+
248
+ **How to use**:
249
+ - Pass an array of task objects (NOT strings)
250
+ - Each task must have a `title` field with short, explanatory description
251
+ - Example format:
252
+ ```json
253
+ {
254
+ "body_params": {
255
+ "tasks": [
256
+ {"title": "Research API requirements"},
257
+ {"title": "Design data schema"},
258
+ {"title": "Implement endpoints"},
259
+ {"title": "Write tests"},
260
+ {"title": "Deploy to staging"}
261
+ ]
262
+ }
263
+ }
264
+ ```
265
+ **Important**:
266
+ - Include ALL steps needed from start to finish
267
+ - Each title should be clear and actionable (3-6 words)
268
+ - Do NOT pass plain strings like `["Task 1", "Task 2"]` - must be objects!
269
+
270
+ ---
271
+
272
+ #### **2. xpget-agent-plan** - View Current Plan
273
+ **When to use**: Before deciding what to do next; to check progress
274
+
275
+ **Returns**:
276
+ - All tasks with their IDs, titles, and completion status
277
+ - Use this to know what's done and what remains
278
+
279
+ **No parameters needed** - just call the tool
280
+
281
+ ---
282
+
283
+ #### **3. xpcomplete-agent-plan-item** - Mark Task Complete
284
+ **When to use**: **IMMEDIATELY** after finishing each task (NOT before!)
285
+
286
+ **How to use**:
287
+ ```json
288
+ {
289
+ "body_params": {
290
+ "id": "task-uuid-from-plan"
291
+ }
292
+ }
293
+ ```
294
+ **CRITICAL**:
295
+ - Only mark complete AFTER work is actually done
296
+ - This is MANDATORY for progress tracking
297
+ - Get the task ID from `xpget-agent-plan` results
298
+
299
+ ---
300
+
301
+ #### **4. xpadd-new-agent-plan-item** - Add Discovered Task
302
+ **When to use**: When you discover additional work needed during execution
303
+
304
+ **How to use**:
305
+ ```json
306
+ {
307
+ "body_params": {
308
+ "title": "Validate input schemas",
309
+ "completed": false
310
+ }
311
+ }
312
+ ```
313
+ ---
314
+
315
+ #### **5. xpupdate-agent-plan-item** - Modify Existing Task
316
+ **When to use**: When task details change or need clarification
317
+
318
+ **How to use**:
319
+ ```json
320
+ {
321
+ "body_params": {
322
+ "id": "task-uuid",
323
+ "title": "Updated description",
324
+ "completed": true
325
+ }
326
+ }
327
+ ```
328
+ (All fields optional except `id`)
329
+
330
+ ---
331
+
332
+ #### **6. xpdelete-agent-plan-item** - Remove Task
333
+ **When to use**: When a task becomes unnecessary or redundant
334
+
335
+ **How to use**:
336
+ ```json
337
+ {
338
+ "body_params": {
339
+ "id": "task-uuid"
340
+ }
341
+ }
342
+ ```
343
+ ---
344
+
345
+ #### **7. xpstart-execution-plan** - Start Plan Execution
346
+ **When to use**: Immediately after creating a plan with `xpcreate-agent-plan`
347
+ **CRITICAL**: Must be called to enable enforcement mode before executing tasks
348
+
349
+ **How to use**:
350
+ ```json
351
+ {
352
+ "body_params": {}
353
+ }
354
+ ```
355
+ **No parameters needed** - just call after plan creation
356
+
357
+ **What it does**:
358
+ - Marks plan as "started"
359
+ - Enables enforcement mode if `enforce` flag is true
360
+ - Allows plan execution to proceed
361
+
362
+ ---
363
+
364
+ #### **8. xpask-for-information** - Ask User a Question
365
+ **When to use**: When you need information from the user during plan execution
366
+ **PREREQUISITE**: Plan must be started (running) first
367
+
368
+ **How to use**:
369
+ ```json
370
+ {
371
+ "body_params": {
372
+ "question": "What is the customer email address?"
373
+ }
374
+ }
375
+ ```
376
+
377
+ **What it does**:
378
+ - Sets `question_raised` flag to true
379
+ - Prints the question for the user
380
+ - Keeps enforcement active (does NOT pause execution)
381
+ - Returns waiting status
382
+
383
+ ---
384
+
385
+ ### **Best Practices**
386
+
387
+ ✅ **DO:**
388
+ - Create comprehensive plans with ALL necessary steps
389
+ - **START** the plan with `xpstart-execution-plan` after creating it
390
+ - Use descriptive, actionable task titles
391
+ - Check plan before each action to stay oriented
392
+ - Mark tasks complete immediately after finishing them
393
+ - Ask for user information when needed with `xpask-for-information`
394
+ - Call plan tools **sequentially** (one at a time, never in parallel)
395
+
396
+ ❌ **DON'T:**
397
+ - Mark tasks complete before they're actually done
398
+ - Pass plain string arrays - must be objects with `title` field
399
+ - Call plan tools in parallel with each other
400
+ - Skip checking the plan between major steps
401
+ - Forget to mark completed tasks
402
+
403
+ ---
404
+
405
+ ### **Example Complete Workflow**
406
+
407
+ ```
408
+ 1. User: "Build a REST API for user management"
409
+
410
+ 2. Call: xpcreate-agent-plan
411
+ tasks: [
412
+ {"title": "Design user schema"},
413
+ {"title": "Create database migration"},
414
+ {"title": "Implement CRUD endpoints"},
415
+ {"title": "Add authentication"},
416
+ {"title": "Write integration tests"}
417
+ ]
418
+
419
+ 3. Call: xpstart-execution-plan
420
+ → Plan now started, enforcement enabled (if enforce=true)
421
+
422
+ 4. Call: xpget-agent-plan
423
+ → See: Task 1 (ID: abc-123) - Design user schema - Not complete
424
+
425
+ 5. [Realize need user input] Call: xpask-for-information
426
+ question: "Which database should we use - PostgreSQL or MySQL?"
427
+ → question_raised=true, waiting for response
428
+
429
+ 6. [After user responds, do the work: Design schema]
430
+
431
+ 7. Call: xpcomplete-agent-plan-item
432
+ id: "abc-123"
433
+
434
+ 8. Call: xpget-agent-plan
435
+ → See: Task 1 ✓ complete, Task 2 next...
436
+
437
+ 9. [Continue through remaining tasks]
438
+ ```
439
+ **Remember**: The plan is your roadmap. Check it often, update it as needed, and always mark tasks complete when done!
440
+ </important_planning_instructions>
441
+ """
442
+
443
+ # add the expected output guidance
444
+ if not "expected_output" in args:
445
+ args["expected_output"] = ""
446
+ args["expected_output"] += "\nAll planned tasks completed and marked as done."
447
+
448
+ # add the plan to additional_context
449
+ if not "additional_context" in args:
450
+ args["additional_context"] = ""
451
+
452
+ plan_str = task.deep_planning.model_dump_json() if task.deep_planning and task.deep_planning.enabled and len(task.deep_planning.tasks) != 0 else "No execution plan, please generate"
453
+ args["additional_context"] += f" \n Current execution plan: {plan_str}"
218
454
 
219
455
  def _load_llm_model(agent: Agent, override: Optional[Dict[str, Any]]) -> Any:
220
456
  """
@@ -278,6 +514,10 @@ def _load_llm_model(agent: Agent, override: Optional[Dict[str, Any]]) -> Any:
278
514
  return env_llm_key or agent.llm_credentials.value
279
515
 
280
516
  llm_args = {}
517
+
518
+ if agent.llm_reasoning_effort and agent.llm_reasoning_effort != LLMReasoningEffort.Medium and agent.model_name and "gpt-5" in agent.model_name.lower():
519
+ llm_args = { "reasoning_effort": agent.llm_reasoning_effort.value }
520
+
281
521
  if agent.llm_api_base and len(agent.llm_api_base) != 0:
282
522
  llm_args["base_url"] = agent.llm_api_base
283
523
 
@@ -579,6 +819,7 @@ async def _resolve_agent_tools(agent: Agent, task: Optional[Task] = None) -> Lis
579
819
  ),
580
820
  include_tools=mcp.allowed_tools or None,
581
821
  timeout_seconds=120,
822
+ tool_name_prefix="mcp_tool"
582
823
  )
583
824
  )
584
825
  elif mcp.url:
@@ -612,10 +853,9 @@ async def _resolve_agent_tools(agent: Agent, task: Optional[Task] = None) -> Lis
612
853
  transport=transport,
613
854
  server_params=params_cls(url=mcp.url, headers=mcp.headers),
614
855
  include_tools=mcp.allowed_tools or None,
615
- timeout_seconds=120
856
+ timeout_seconds=120,
857
+ tool_name_prefix="mcp_tool"
616
858
  )
617
859
  )
618
860
 
619
- return agent.tools.functions + await asyncio.gather(
620
- *[mcp.__aenter__() for mcp in mcp_tools]
621
- )
861
+ return agent.tools.functions + mcp_tools
@@ -8,7 +8,7 @@ from xpander_sdk.modules.tasks.sub_modules.task import Task, TaskUpdateEvent
8
8
  from xpander_sdk.modules.tools_repository.models.mcp import MCPOAuthGetTokenGenericResponse, MCPOAuthGetTokenResponse, MCPOAuthResponseType, MCPServerDetails
9
9
 
10
10
  POLLING_INTERVAL = 1 # every 1s
11
- MAX_WAIT_FOR_LOGIN = 300 # 5 mintutes
11
+ MAX_WAIT_FOR_LOGIN = 600 # 10 mintutes
12
12
 
13
13
  async def push_event(task: Task, event: TaskUpdateEvent):
14
14
  client = APIClient(configuration=task.configuration)
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import asyncio
11
11
  import json
12
+ import json as py_json
12
13
  import os
13
14
  import signal
14
15
  import sys
@@ -19,10 +20,12 @@ from typing import Any, Awaitable, Callable, Optional, Set, Union, List
19
20
  import httpx
20
21
  from httpx_sse import aconnect_sse
21
22
  from loguru import logger
23
+ from pydantic import BaseModel
22
24
 
23
25
  from xpander_sdk.core.module_base import ModuleBase
24
26
  from xpander_sdk.exceptions.module_exception import ModuleException
25
27
  from xpander_sdk.models.configuration import Configuration
28
+ from xpander_sdk.models.shared import OutputFormat
26
29
  from xpander_sdk.modules.agents.models.agent import SourceNodeType
27
30
  from xpander_sdk.modules.tasks.tasks_module import Tasks
28
31
 
@@ -327,6 +330,7 @@ class Events(ModuleBase):
327
330
  agent_worker: DeployedAsset,
328
331
  task: Task,
329
332
  on_execution_request: ExecutionRequestHandler,
333
+ retry_count: Optional[int] = 0,
330
334
  ) -> None:
331
335
  """
332
336
  Handle an incoming task execution request.
@@ -335,6 +339,7 @@ class Events(ModuleBase):
335
339
  agent_worker (DeployedAsset): The deployed asset (agent) to handle the task.
336
340
  task (Task): The task object containing execution details.
337
341
  on_execution_request (ExecutionRequestHandler): The handler function to process the task.
342
+ retry_count (Optional[int]): Current retry attempt count. Defaults to 0.
338
343
  """
339
344
  error = None
340
345
  try:
@@ -348,6 +353,25 @@ class Events(ModuleBase):
348
353
  on_execution_request,
349
354
  task,
350
355
  )
356
+
357
+ # Check if plan is complete, retry if not
358
+ plan_following_status = await task.aget_plan_following_status()
359
+ if not plan_following_status.can_finish:
360
+ # Check if we've exceeded max retries
361
+ if retry_count >= 2: # 0, 1, 2 = 3 total attempts
362
+ logger.warning(f"Failed to complete plan after {retry_count + 1} attempts. Remaining incomplete tasks.")
363
+ return
364
+
365
+ # Recursively call with incremented retry count
366
+ logger.info(f"Plan not complete, retrying (attempt {retry_count + 2}/3)")
367
+ await self.handle_task_execution_request(
368
+ agent_worker,
369
+ task,
370
+ on_execution_request,
371
+ retry_count=retry_count + 1
372
+ )
373
+ return
374
+
351
375
  except Exception as e:
352
376
  logger.exception(f"Execution handler failed - {str(e)}")
353
377
  error = str(e)
@@ -364,6 +388,16 @@ class Events(ModuleBase):
364
388
  ): # let the handler set the status, if not set - mark as completed
365
389
  task.status = AgentExecutionStatus.Completed
366
390
 
391
+ # in case of structured output, return as stringified json
392
+ try:
393
+ if task.output_format == OutputFormat.Json:
394
+ if isinstance(task.result, BaseModel):
395
+ task.result = task.result.model_dump_json()
396
+ if isinstance(task.result, dict) or isinstance(task.result, list):
397
+ task.result = py_json.dumps(task.result)
398
+ except Exception:
399
+ pass
400
+
367
401
  await task.asave()
368
402
  task.tokens = task_used_tokens
369
403
  task.used_tools = task_used_tools