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