ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.8.0__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 (61) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
  3. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
  4. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
  5. ibm_watsonx_orchestrate/agent_builder/agents/types.py +38 -9
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +1 -0
  7. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +25 -10
  8. ibm_watsonx_orchestrate/agent_builder/connections/types.py +19 -11
  9. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
  10. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
  11. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
  13. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
  14. ibm_watsonx_orchestrate/agent_builder/tools/types.py +10 -9
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +35 -7
  17. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
  18. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +35 -25
  19. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  20. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  21. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +12 -12
  22. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  23. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
  24. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
  25. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
  26. ibm_watsonx_orchestrate/cli/commands/environment/types.py +3 -1
  27. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +134 -36
  28. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +42 -11
  29. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
  30. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +36 -20
  31. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
  33. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +59 -10
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
  36. ibm_watsonx_orchestrate/cli/config.py +3 -3
  37. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  38. ibm_watsonx_orchestrate/cli/main.py +5 -0
  39. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  40. ibm_watsonx_orchestrate/client/connections/connections_client.py +5 -30
  41. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
  42. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  43. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  44. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  45. ibm_watsonx_orchestrate/client/utils.py +49 -8
  46. ibm_watsonx_orchestrate/docker/compose-lite.yml +25 -6
  47. ibm_watsonx_orchestrate/docker/default.env +26 -15
  48. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +9 -4
  49. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  50. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  51. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +131 -20
  52. ibm_watsonx_orchestrate/flow_builder/node.py +18 -1
  53. ibm_watsonx_orchestrate/flow_builder/types.py +271 -15
  54. ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
  55. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  56. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/METADATA +5 -5
  57. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/RECORD +60 -56
  58. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  59. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/WHEEL +0 -0
  60. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/entry_points.txt +0 -0
  61. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ import uuid
5
5
  import yaml
6
6
  from pydantic import BaseModel, Field, SerializeAsAny
7
7
 
8
- from .types import EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec
8
+ from .types import EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec, DocProcSpec, DecisionsNodeSpec
9
9
  from .data_map import DataMap
10
10
 
11
11
  class Node(BaseModel):
@@ -78,6 +78,8 @@ class UserNode(Node):
78
78
  description: str | None = None,
79
79
  default: Any | None = None,
80
80
  option: UserFieldOption | None = None,
81
+ min: Any | None = None,
82
+ max: Any | None = None,
81
83
  is_list: bool = False,
82
84
  custom: dict[str, Any] | None = None,
83
85
  widget: str | None = None):
@@ -88,6 +90,8 @@ class UserNode(Node):
88
90
  description=description,
89
91
  default=default,
90
92
  option=option,
93
+ min=min,
94
+ max=max,
91
95
  is_list=is_list,
92
96
  custom=custom,
93
97
  widget=widget)
@@ -105,7 +109,20 @@ class PromptNode(Node):
105
109
 
106
110
  def get_spec(self) -> PromptNodeSpec:
107
111
  return cast(PromptNodeSpec, self.spec)
112
+
113
+ class DocProcNode(Node):
114
+ def __repr__(self):
115
+ return f"DocProcNode(name='{self.spec.name}', description='{self.spec.description}')"
108
116
 
117
+ def get_spec(self) -> DocProcSpec:
118
+ return cast(DocProcSpec, self.spec)
119
+ class DecisionsNode(Node):
120
+ def __repr__(self):
121
+ return f"DecisionsNode(name='{self.spec.name}', description='{self.spec.description}')"
122
+
123
+ def get_spec(self) -> DecisionsNodeSpec:
124
+ return cast(DecisionsNodeSpec, self.spec)
125
+
109
126
  class NodeInstance(BaseModel):
110
127
  node: Node
111
128
  id: str # unique id of this task instance
@@ -1,5 +1,7 @@
1
1
  from dataclasses import dataclass
2
- from enum import Enum
2
+ from enum import Enum, StrEnum, auto
3
+ from datetime import date
4
+ import numbers
3
5
  import inspect
4
6
  import logging
5
7
  from typing import (
@@ -116,7 +118,7 @@ def _to_json_from_output_schema(schema: Union[ToolResponseBody, SchemaRef]) -> d
116
118
  return model_spec
117
119
 
118
120
  class NodeSpec(BaseModel):
119
- kind: Literal["node", "tool", "user", "agent", "flow", "start", "decisions", "prompt", "branch", "wait", "foreach", "loop", "userflow", "end"] = "node"
121
+ kind: Literal["node", "tool", "user", "agent", "flow", "start", "decisions", "prompt", "branch", "wait", "foreach", "loop", "userflow", "end", "docproc" ] = "node"
120
122
  name: str
121
123
  display_name: str | None = None
122
124
  description: str | None = None
@@ -162,6 +164,24 @@ class NodeSpec(BaseModel):
162
164
 
163
165
  return model_spec
164
166
 
167
+ class DocProcTask(StrEnum):
168
+ '''
169
+ Possible names for the Document processing task parameter
170
+ '''
171
+ text_extraction = auto()
172
+
173
+ class DocProcSpec(NodeSpec):
174
+ task: DocProcTask = Field(description='The document processing operation name', default=DocProcTask.text_extraction)
175
+
176
+ def __init__(self, **data):
177
+ super().__init__(**data)
178
+ self.kind = "docproc"
179
+
180
+ def to_json(self) -> dict[str, Any]:
181
+ model_spec = super().to_json()
182
+ model_spec["task"] = self.task
183
+ return model_spec
184
+
165
185
  class StartNodeSpec(NodeSpec):
166
186
  def __init__(self, **data):
167
187
  super().__init__(**data)
@@ -296,6 +316,8 @@ class UserField(BaseModel):
296
316
  description: str | None = None
297
317
  default: Any | None = None
298
318
  option: UserFieldOption | None = None
319
+ min: Any | None = None,
320
+ max: Any | None = None,
299
321
  is_list: bool = False
300
322
  custom: dict[str, Any] | None = None
301
323
  widget: str | None = None
@@ -314,6 +336,10 @@ class UserField(BaseModel):
314
336
  model_spec["description"] = self.description
315
337
  if self.default:
316
338
  model_spec["default"] = self.default
339
+ if self.min:
340
+ model_spec["min"] = self.min
341
+ if self.max:
342
+ model_spec["min"] = self.max
317
343
  if self.is_list:
318
344
  model_spec["is_list"] = self.is_list
319
345
  if self.option:
@@ -356,7 +382,10 @@ class UserNodeSpec(NodeSpec):
356
382
  display_name: str | None = None,
357
383
  description: str | None = None,
358
384
  default: Any | None = None,
359
- option: list[str] | None = None, is_list: bool = False,
385
+ option: list[str] | None = None,
386
+ min: Any | None = None,
387
+ max: Any | None = None,
388
+ is_list: bool = False,
360
389
  custom: dict[str, Any] | None = None,
361
390
  widget: str | None = None):
362
391
  userfield = UserField(name=name,
@@ -366,6 +395,8 @@ class UserNodeSpec(NodeSpec):
366
395
  description=description,
367
396
  default=default,
368
397
  option=option,
398
+ min=min,
399
+ max=max,
369
400
  is_list=is_list,
370
401
  custom=custom,
371
402
  widget=widget)
@@ -402,6 +433,8 @@ class UserNodeSpec(NodeSpec):
402
433
  default=prop_schema.default,
403
434
  option=self.setup_field_options(prop_schema.title, prop_schema.enum),
404
435
  is_list=prop_schema.type == "array",
436
+ min=prop_schema.minimum,
437
+ max=prop_schema.maximum,
405
438
  custom=prop_schema.model_extra))
406
439
 
407
440
  def setup_field_options(self, name: str, enums: List[str]) -> UserFieldOption:
@@ -415,6 +448,7 @@ class UserNodeSpec(NodeSpec):
415
448
 
416
449
  class AgentNodeSpec(ToolNodeSpec):
417
450
  message: str | None = Field(default=None, description="The instructions for the task.")
451
+ title: str | None = Field(default=None, description="The title of the message.")
418
452
  guidelines: str | None = Field(default=None, description="The guidelines for the task.")
419
453
  agent: str
420
454
 
@@ -430,6 +464,8 @@ class AgentNodeSpec(ToolNodeSpec):
430
464
  model_spec["guidelines"] = self.guidelines
431
465
  if self.agent:
432
466
  model_spec["agent"] = self.agent
467
+ if self.title:
468
+ model_spec["title"] = self.title
433
469
  return model_spec
434
470
 
435
471
  class PromptLLMParameters(BaseModel):
@@ -479,6 +515,7 @@ class PromptNodeSpec(NodeSpec):
479
515
  model_spec["llm_parameters"] = self.llm_parameters.to_json()
480
516
 
481
517
  return model_spec
518
+
482
519
 
483
520
  class Expression(BaseModel):
484
521
  '''An expression could return a boolean or a value'''
@@ -553,10 +590,9 @@ class WaitNodeSpec(FlowControlNodeSpec):
553
590
  return my_dict
554
591
 
555
592
  class FlowSpec(NodeSpec):
556
-
557
-
558
593
  # who can initiate the flow
559
594
  initiators: Sequence[str] = [ANY_USER]
595
+ schedulable: bool = False
560
596
 
561
597
  def __init__(self, **kwargs):
562
598
  super().__init__(**kwargs)
@@ -566,6 +602,8 @@ class FlowSpec(NodeSpec):
566
602
  model_spec = super().to_json()
567
603
  if self.initiators:
568
604
  model_spec["initiators"] = self.initiators
605
+
606
+ model_spec["schedulable"] = self.schedulable
569
607
 
570
608
  return model_spec
571
609
 
@@ -631,11 +669,11 @@ class TaskData(NamedTuple):
631
669
 
632
670
  class TaskEventType(Enum):
633
671
 
634
- ON_TASK_WAIT = "on_task_wait" # the task is waiting for inputs before proceeding
635
- ON_TASK_START = "on_task_start"
636
- ON_TASK_END = "on_task_end"
637
- ON_TASK_STREAM = "on_task_stream"
638
- ON_TASK_ERROR = "on_task_error"
672
+ ON_TASK_WAIT = "task:on_task_wait" # the task is waiting for inputs before proceeding
673
+ ON_TASK_START = "task:on_task_start"
674
+ ON_TASK_END = "task:on_task_end"
675
+ ON_TASK_STREAM = "task:on_task_stream"
676
+ ON_TASK_ERROR = "task:on_task_error"
639
677
 
640
678
  class FlowData(BaseModel):
641
679
  '''This class represents the data that is passed between tasks in a flow.'''
@@ -667,9 +705,9 @@ class FlowContext(BaseModel):
667
705
 
668
706
  class FlowEventType(Enum):
669
707
 
670
- ON_FLOW_START = "on_flow_start"
671
- ON_FLOW_END = "on_flow_end"
672
- ON_FLOW_ERROR = "on_flow_error"
708
+ ON_FLOW_START = "flow:on_flow_start"
709
+ ON_FLOW_END = "flow:on_flow_end"
710
+ ON_FLOW_ERROR = "flow:on_flow_error"
673
711
 
674
712
 
675
713
  @dataclass
@@ -691,9 +729,227 @@ class Assignment(BaseModel):
691
729
  e.g. "node.input.name" or "=f'{node.output.name}_{node.output.id}'"
692
730
 
693
731
  '''
694
- target: str
695
- source: str
732
+ target_variable: str
733
+ value_expression: str | None = None
734
+ has_no_value: bool = False
735
+ default_value: Any | None = None
736
+ metadata: dict = Field(default_factory=dict[str, Any])
737
+
738
+ class LanguageCode(StrEnum):
739
+ '''
740
+ The ISO-639 language codes understood by Document Processing functions.
741
+ A special 'en_hw' code is used to enable an English handwritten model.
742
+ '''
743
+ en = auto()
744
+ fr = auto()
745
+ en_hw = auto()
746
+
747
+ class File(BaseModel):
748
+ '''
749
+ This class represents the input of a Document processing task.
750
+
751
+ Attributes:
752
+ document_ref (bytes|str): This is either a URL to the location of the document bytes or an ID that we use to resolve the location of the document
753
+ language (LanguageCode): Optional language code used when processing the input document
754
+ '''
755
+ # This is declared as bytes but the runtime will understand if a URL is send in as input.
756
+ # We need to use bytes here for Chat-with-doc to recognize the input as a File.
757
+ document_ref: bytes | str = Field(
758
+ description="Either an ID or a URL identifying the document to be used.",
759
+ title='Document reference',
760
+ default=None,
761
+ json_schema_extra={"format": "binary"})
762
+ language: Optional[LanguageCode] = Field(
763
+ description='Optional language code of the document, defaults to "en"',
764
+ title='Document language code',
765
+ default=LanguageCode.en)
766
+
767
+ class TextExtraction(BaseModel):
768
+ '''
769
+ This class represents the output generated by a "text_extraction" document processing (docproc) operation.
770
+ Attributes:
771
+ text (str): the text extracted from the input document.
772
+ '''
773
+ text: str = Field(description='The text extracted from the input document', title='Text extraction')
774
+
775
+ class TextExtractionResponse(BaseModel):
776
+ '''
777
+ The text extraction operation response.
778
+ Attributes:
779
+ output (TextExtraction): a wrapper for the text extraction response
780
+ '''
781
+ output: TextExtraction = Field(description='The text extraction response')
782
+
783
+
784
+ class DecisionsCondition(BaseModel):
785
+ _condition: str | None = None
786
+
787
+ def greater_than(self, value: Union[numbers.Number, date, str]) -> Self:
788
+ self._check_type_is_number_or_date_or_str(value)
789
+ self._condition = f"> {self._format_value(value)}"
790
+ return self
791
+
792
+ def greater_than_or_equal(self, value: Union[numbers.Number, date, str]) -> Self:
793
+ self._check_type_is_number_or_date_or_str(value)
794
+ self._condition = f">= {self._format_value(value)}"
795
+ return self
796
+
797
+ def less_than(self, value: Union[numbers.Number, date, str]) -> Self:
798
+ self._check_type_is_number_or_date_or_str(value)
799
+ self._condition = f"< {self._format_value(value)}"
800
+ return self
801
+
802
+ def less_than_or_equal(self, value: Union[numbers.Number, date, str]) -> Self:
803
+ self._check_type_is_number_or_date_or_str(value)
804
+ self._condition = f"<= {self._format_value(value)}"
805
+ return self
806
+
807
+ def equal(self, value: Union[numbers.Number, date, str]) -> Self:
808
+ self._check_type_is_number_or_date_or_str(value)
809
+ self._condition = f"== {self._format_value(value)}"
810
+ return self
811
+
812
+ def not_equal(self, value: Union[numbers.Number, date, str]) -> Self:
813
+ self._check_type_is_number_or_date_or_str(value)
814
+ self._condition = f"== {self._format_value(value)}"
815
+ return self
816
+
817
+ def contains(self, value: str) -> Self:
818
+ self._check_type_is_str(value)
819
+ self._condition = f"contains {self._format_value(value)}"
820
+ return self
821
+
822
+ def not_contains(self, value: str) -> Self:
823
+ self._check_type_is_str(value)
824
+ self._condition = f"doesNotContain {self._format_value(value)}"
825
+ return self
826
+
827
+ def is_in(self, value: str) -> Self:
828
+ self._check_type_is_str(value)
829
+ self._condition = f"in {self._format_value(value)}"
830
+ return self
831
+
832
+ def is_not_in(self, value: str) -> Self:
833
+ self._check_type_is_str(value)
834
+ self._condition = f"notIn {self._format_value(value)}"
835
+ return self
836
+
837
+ def startswith(self, value: str) -> Self:
838
+ self._check_type_is_str(value)
839
+ self._condition = f"startsWith {self._format_value(value)}"
840
+ return self
841
+
842
+ def endswith(self, value: str) -> Self:
843
+ self._check_type_is_str(value)
844
+ self._condition = f"endsWith {self._format_value(value)}"
845
+ return self
846
+
847
+
848
+ def in_range(self, startValue: Union[numbers.Number, date], endValue: Union[numbers.Number, date],
849
+ startsInclusive: bool = False, endsInclusive: bool = False) -> Self:
850
+ self._check_type_is_number_or_date_or_str(startValue)
851
+ self._check_type_is_number_or_date_or_str(endValue)
852
+ if type(startValue) is not type(endValue):
853
+ raise TypeError("startValue and endValue must be of the same type")
854
+ start_op = "[" if startsInclusive else "(" # [ is inclusive, ( is exclusive
855
+ end_op = "]" if endsInclusive else ")"
856
+ self._condition = f"{start_op}{self._format_value(startValue)}:{self._format_value(endValue)}{end_op}"
857
+ return self
858
+
859
+ def _check_type_is_number_or_date(self, value: Union[numbers.Number, date]):
860
+ if not isinstance(value, (numbers.Number, date)):
861
+ raise TypeError("Value must be a number or a date")
862
+
863
+ def _check_type_is_number_or_date_or_str(self, value: Union[numbers.Number, date, str]):
864
+ if not isinstance(value, (numbers.Number, date, str)):
865
+ raise TypeError("Value must be a number or a date or a string")
866
+
867
+ def _check_type_is_str(self, value: str):
868
+ if not isinstance(value, str):
869
+ raise TypeError("Value must be a string")
870
+
871
+ @staticmethod
872
+ def _format_value(value: Union[numbers.Number, date, str]):
873
+ if isinstance(value, numbers.Number):
874
+ return f"{value}"
875
+ if isinstance(value, date):
876
+ return f"\"{value.strftime('%B %d, %Y')}\""
877
+ return f"\"{value}\""
878
+
879
+ def condition(self):
880
+ return self._condition
881
+
882
+
883
+
884
+ class DecisionsRule(BaseModel):
885
+ '''
886
+ A set of decisions rules.
887
+ '''
888
+ _conditions: dict[str, str]
889
+ _actions: dict[str, Union[numbers.Number, str]]
890
+
891
+ def __init__(self, **data):
892
+ super().__init__(**data)
893
+ self._conditions = {}
894
+ self._actions = {}
895
+
896
+ def condition(self, key: str, cond: DecisionsCondition) -> Self:
897
+ self._conditions[key] = cond.condition()
898
+ return self
696
899
 
900
+ def action(self, key: str, value: Union[numbers.Number, date, str]) -> Self:
901
+ if isinstance(value, date):
902
+ self._actions[key] = value.strftime("%B %d, %Y")
903
+ return self
904
+ self._actions[key] = value
905
+ return self
906
+
907
+ def to_json(self) -> dict[str, Any]:
908
+ '''
909
+ Serialize the rules into JSON object
910
+ '''
911
+ model_spec = {}
912
+ if self._conditions:
913
+ model_spec["conditions"] = self._conditions
914
+ if self._actions:
915
+ model_spec["actions"] = self._actions
916
+ return model_spec
917
+
918
+
919
+ class DecisionsNodeSpec(NodeSpec):
920
+ '''
921
+ Node specification for Decision Table
922
+ '''
923
+ locale: str | None = None
924
+ rules: list[DecisionsRule]
925
+ default_actions: dict[str, Union[int, float, complex, str]] | None
926
+
927
+ def __init__(self, **data):
928
+ super().__init__(**data)
929
+ self.kind = "decisions"
930
+
931
+ def default_action(self, key: str, value: Union[int, float, complex, date, str]) -> Self:
932
+ '''
933
+ create a new default action
934
+ '''
935
+ if isinstance(value, date):
936
+ self.default_actions[key] = value.strftime("%B %d, %Y")
937
+ return self
938
+ self.default_actions[key] = value
939
+ return self
940
+
941
+ def to_json(self) -> dict[str, Any]:
942
+ model_spec = super().to_json()
943
+ if self.locale:
944
+ model_spec["locale"] = self.locale
945
+ if self.rules:
946
+ model_spec["rules"] = [rule.to_json() for rule in self.rules]
947
+ if self.default_actions:
948
+ model_spec["default_actions"] = self.default_actions
949
+
950
+ return model_spec
951
+
952
+
697
953
  def extract_node_spec(
698
954
  fn: Callable | PythonTool,
699
955
  name: Optional[str] = None,
@@ -87,9 +87,15 @@ def _get_tool_request_body(schema_obj: JsonSchemaObject) -> ToolRequestBody:
87
87
  return None
88
88
 
89
89
  if isinstance(schema_obj, JsonSchemaObject):
90
- request_obj = ToolRequestBody(type='object', properties=schema_obj.properties, required=schema_obj.required)
91
- if schema_obj.model_extra:
92
- request_obj.__pydantic_extra__ = schema_obj.model_extra
90
+ if schema_obj.type == "object":
91
+ request_obj = ToolRequestBody(type='object', properties=schema_obj.properties, required=schema_obj.required)
92
+ if schema_obj.model_extra:
93
+ request_obj.__pydantic_extra__ = schema_obj.model_extra
94
+ else: # we need to wrap a simple type with an object
95
+ request_obj = ToolRequestBody(type='object', properties={}, required=[])
96
+ request_obj.properties["data"] = schema_obj
97
+ if schema_obj.model_extra:
98
+ request_obj.__pydantic_extra__ = schema_obj.model_extra
93
99
 
94
100
  return request_obj
95
101
 
@@ -162,17 +168,27 @@ async def import_flow_model(model):
162
168
 
163
169
  return tool_id
164
170
 
165
- def import_flow_support_tools():
171
+ def import_flow_support_tools(model):
166
172
 
167
173
  if not is_local_dev():
168
174
  # we can't import support tools into non-local environments yet
169
- return
175
+ return []
176
+
177
+
178
+ schedulable = False
179
+ if "schedulable" in model["spec"]:
180
+ schedulable = model["spec"]["schedulable"]
170
181
 
171
182
  client = instantiate_client(TempusClient)
172
183
 
173
184
  logger.info(f"Import 'get_flow_status' tool spec...")
174
185
  tools = [create_flow_status_tool("i__get_flow_status_intrinsic_tool__")]
175
186
 
187
+ if schedulable:
188
+ get_schedule_tool = create_get_schedule_tool("i__get_schedule_intrinsic_tool__")
189
+ delete_schedule_tool = create_delete_schedule_tool("i__delete_schedule_intrinsic_tool__")
190
+ tools.extend([get_schedule_tool, delete_schedule_tool])
191
+
176
192
  return tools
177
193
 
178
194
  # Assisted by watsonx Code Assistant
@@ -187,7 +203,7 @@ def create_flow_status_tool(flow_status_tool: str, TEMPUS_ENDPOINT: str="http://
187
203
  )
188
204
 
189
205
  openapi_binding = OpenApiToolBinding(
190
- http_path="/flows",
206
+ http_path="/v1/flows",
191
207
  http_method="GET",
192
208
  security=[],
193
209
  servers=[TEMPUS_ENDPOINT]
@@ -213,3 +229,102 @@ def create_flow_status_tool(flow_status_tool: str, TEMPUS_ENDPOINT: str="http://
213
229
 
214
230
  return OpenAPITool(spec=spec)
215
231
 
232
+
233
+ def create_get_schedule_tool(name: str, TEMPUS_ENDPOINT: str="http://wxo-tempus-runtime:9044") -> dict:
234
+
235
+ spec = ToolSpec(
236
+ name=name,
237
+ description="Use this tool to show the current schedules.",
238
+ permission='read_only',
239
+ display_name= "Get Schedules"
240
+ )
241
+
242
+ openapi_binding = OpenApiToolBinding(
243
+ http_path="/v1/schedules/simple",
244
+ http_method="GET",
245
+ security=[],
246
+ servers=[TEMPUS_ENDPOINT]
247
+ )
248
+
249
+ spec.binding = ToolBinding(openapi=openapi_binding)
250
+ # Input Schema
251
+ properties = {
252
+ "query_schedule_id": {
253
+ "type": "string",
254
+ "title": "schedule_id",
255
+ "description": "Identifies the schedule instance.",
256
+ "in": "query"
257
+ },
258
+ "query_schedule_name": {
259
+ "type": "string",
260
+ "title": "schedule_name",
261
+ "description": "Identifies the schedule name.",
262
+ "in": "query"
263
+ },
264
+ }
265
+
266
+ spec.input_schema = ToolRequestBody(
267
+ type='object',
268
+ properties=properties,
269
+ required=[]
270
+ )
271
+
272
+ response_properties = {
273
+ "schedule_id": {
274
+ "type": "string",
275
+ },
276
+ "schedule_name": {
277
+ "type": "string",
278
+ },
279
+ "schedule_data": {
280
+ "type": "string",
281
+ },
282
+ "schedule_time": {
283
+ "type": "string",
284
+ }
285
+ }
286
+
287
+ spec.output_schema = ToolResponseBody(type='object',
288
+ properties=response_properties,
289
+ description='Return the information about the schedule.')
290
+
291
+ return OpenAPITool(spec=spec)
292
+
293
+
294
+ def create_delete_schedule_tool(name: str, TEMPUS_ENDPOINT: str="http://wxo-tempus-runtime:9044") -> dict:
295
+
296
+ spec = ToolSpec(
297
+ name=name,
298
+ description="Use this tool to delete/remove a schedule based on the schedule_id.",
299
+ permission='read_only',
300
+ display_name= "Delete Schedule"
301
+ )
302
+
303
+ openapi_binding = OpenApiToolBinding(
304
+ http_path="/v1/schedules/{schedule_id}",
305
+ http_method="DELETE",
306
+ security=[],
307
+ servers=[TEMPUS_ENDPOINT]
308
+ )
309
+
310
+ spec.binding = ToolBinding(openapi=openapi_binding)
311
+ # Input Schema
312
+ properties = {
313
+ "path_schedule_id": {
314
+ "type": "string",
315
+ "title": "schedule_id",
316
+ "description": "Identifies the schedule instance.",
317
+ "in": "query"
318
+ }
319
+ }
320
+
321
+ spec.input_schema = ToolRequestBody(
322
+ type='object',
323
+ properties=properties,
324
+ required=[]
325
+ )
326
+
327
+ spec.output_schema = ToolResponseBody(type='object',
328
+ description='Schedule deleted.')
329
+
330
+ return OpenAPITool(spec=spec)
@@ -0,0 +1,23 @@
1
+ import logging
2
+ import sys
3
+ import os
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ class BadRequest(Exception):
8
+ def __init__(self, message: str):
9
+ super().__init__(message)
10
+ self.message = message
11
+ logger.error(message)
12
+
13
+ # We need to exit to avoid getting 2 error messages printed
14
+ # We don't want to exit while running tests
15
+ # Only exit if not running in a test and no --debug
16
+ if not self._in_test() and "--debug" not in sys.argv:
17
+ sys.exit(1)
18
+
19
+ def _in_test(self):
20
+ return "PYTEST_CURRENT_TEST" in os.environ
21
+
22
+ def __str__(self):
23
+ return self.message
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibm-watsonx-orchestrate
3
- Version: 1.7.0a0
3
+ Version: 1.8.0
4
4
  Summary: IBM watsonx.orchestrate SDK
5
5
  Author-email: IBM <support@ibm.com>
6
6
  License: MIT License
@@ -10,13 +10,13 @@ Requires-Dist: certifi>=2024.8.30
10
10
  Requires-Dist: click<8.2.0,>=8.0.0
11
11
  Requires-Dist: docstring-parser<1.0,>=0.16
12
12
  Requires-Dist: httpx<1.0.0,>=0.28.1
13
- Requires-Dist: ibm-cloud-sdk-core>=3.22.0
14
- Requires-Dist: ibm-watsonx-orchestrate-evaluation-framework==1.0.2
13
+ Requires-Dist: ibm-cloud-sdk-core>=3.24.2
14
+ Requires-Dist: ibm-watsonx-orchestrate-evaluation-framework==1.0.8
15
15
  Requires-Dist: jsonref==1.1.0
16
16
  Requires-Dist: jsonschema<5.0.0,>=4.23.0
17
- Requires-Dist: langchain-community<1.0.0,>=0.3.12
17
+ Requires-Dist: langchain-core<=0.3.63
18
+ Requires-Dist: langsmith<=0.3.45
18
19
  Requires-Dist: munch>=4.0.0
19
- Requires-Dist: numpy>=1.26.0
20
20
  Requires-Dist: packaging>=24.2
21
21
  Requires-Dist: pydantic<3.0.0,>=2.10.3
22
22
  Requires-Dist: pyjwt<3.0.0,>=2.10.1