ibm-watsonx-orchestrate 1.7.0b1__py3-none-any.whl → 1.8.0b1__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 (30) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -2
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +29 -1
  3. ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -2
  4. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
  5. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +34 -23
  6. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  7. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -8
  8. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  9. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
  10. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
  11. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -1
  12. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +16 -6
  13. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +20 -2
  14. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +10 -8
  15. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +3 -9
  16. ibm_watsonx_orchestrate/cli/main.py +3 -0
  17. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  18. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
  19. ibm_watsonx_orchestrate/client/utils.py +49 -9
  20. ibm_watsonx_orchestrate/docker/compose-lite.yml +19 -2
  21. ibm_watsonx_orchestrate/docker/default.env +10 -6
  22. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +8 -5
  23. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +47 -7
  24. ibm_watsonx_orchestrate/flow_builder/node.py +7 -1
  25. ibm_watsonx_orchestrate/flow_builder/types.py +168 -65
  26. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/METADATA +2 -2
  27. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/RECORD +30 -26
  28. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/WHEEL +0 -0
  29. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/entry_points.txt +0 -0
  30. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -46,15 +46,7 @@ _ALWAYS_UNSET: set[str] = {
46
46
 
47
47
  def define_saas_wdu_runtime(value: str = "none") -> None:
48
48
  cfg = Config()
49
-
50
- current_config_file_values = cfg.get(USER_ENV_CACHE_HEADER)
51
- current_config_file_values["SAAS_WDU_RUNTIME"] = value
52
-
53
- cfg.save(
54
- {
55
- USER_ENV_CACHE_HEADER: current_config_file_values
56
- }
57
- )
49
+ cfg.write(USER_ENV_CACHE_HEADER,"SAAS_WDU_RUNTIME",value)
58
50
 
59
51
  def ensure_docker_installed() -> None:
60
52
  try:
@@ -390,6 +382,8 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
390
382
  "up",
391
383
  "--scale",
392
384
  "ui=0",
385
+ "--scale",
386
+ "cpe=0",
393
387
  "-d",
394
388
  "--remove-orphans",
395
389
  ]
@@ -1,3 +1,4 @@
1
+
1
2
  import typer
2
3
  import sys
3
4
 
@@ -14,6 +15,7 @@ from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import chann
14
15
  from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
15
16
  from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
16
17
  from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_command import evaluation_app
18
+ from ibm_watsonx_orchestrate.cli.commands.copilot.copilot_command import copilot_app
17
19
  from ibm_watsonx_orchestrate.cli.init_helper import init_callback
18
20
 
19
21
  import urllib3
@@ -38,6 +40,7 @@ app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Dev
38
40
  app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
39
41
  app.add_typer(channel_app, name="channels", help="Configure channels where your agent can exist on (such as embedded webchat)")
40
42
  app.add_typer(evaluation_app, name="evaluations", help='Evaluate the performance of your agents in your active env')
43
+ app.add_typer(copilot_app, name="copilot", help='Access AI powered assistance to help refine your agents')
41
44
  app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
42
45
 
43
46
  if __name__ == "__main__":
@@ -3,6 +3,7 @@ import json
3
3
  import requests
4
4
  from abc import ABC, abstractmethod
5
5
  from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
6
+ from typing_extensions import List
6
7
 
7
8
 
8
9
  class ClientAPIException(requests.HTTPError):
@@ -62,6 +63,17 @@ class BaseAPIClient:
62
63
  self._check_response(response)
63
64
  return response.json() if response.text else {}
64
65
 
66
+ def _post_nd_json(self, path: str, data: dict = None, files: dict = None) -> List[dict]:
67
+ url = f"{self.base_url}{path}"
68
+ response = requests.post(url, headers=self._get_headers(), json=data, files=files)
69
+ self._check_response(response)
70
+
71
+ res = []
72
+ if response.text:
73
+ for line in response.text.splitlines():
74
+ res.append(json.loads(line))
75
+ return res
76
+
65
77
  def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
66
78
  url = f"{self.base_url}{path}"
67
79
  # Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
@@ -0,0 +1,67 @@
1
+ from typing import Dict, Any
2
+ from uuid import uuid4
3
+
4
+ from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
5
+
6
+
7
+ class CPEClient(BaseAPIClient):
8
+ """
9
+ Client to handle CRUD operations for Conversational Prompt Engineering Service
10
+ """
11
+
12
+ def __init__(self, *args, **kwargs):
13
+ self.chat_id = str(uuid4())
14
+ super().__init__(*args, **kwargs)
15
+ self.base_url = kwargs.get("base_url", self.base_url)
16
+ self.chat_model_name = 'llama-3-3-70b-instruct'
17
+
18
+ def _get_headers(self) -> dict:
19
+ return {
20
+ "chat_id": self.chat_id
21
+ }
22
+
23
+
24
+ def submit_pre_cpe_chat(self, user_message: str | None =None, tools: Dict[str, Any] = None, agents: Dict[str, Any] = None) -> dict:
25
+ payload = {
26
+ "message": user_message,
27
+ "tools": tools,
28
+ "agents": agents,
29
+ "chat_id": self.chat_id,
30
+ "chat_model_name": self.chat_model_name
31
+ }
32
+
33
+ response = self._post_nd_json("/wxo-cpe/create-agent", data=payload)
34
+
35
+ if response:
36
+ return response[-1]
37
+
38
+
39
+ def init_with_context(self, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
40
+ payload = {
41
+ "context_data": context_data,
42
+ "chat_id": self.chat_id
43
+ }
44
+
45
+ if model:
46
+ payload["target_model_name"] = model
47
+
48
+ response = self._post_nd_json("/wxo-cpe/init_cpe_from_wxo", data=payload)
49
+
50
+ if response:
51
+ return response[-1]
52
+
53
+
54
+ def invoke(self, prompt: str, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
55
+ payload = {
56
+ "prompt": prompt,
57
+ "context_data": context_data,
58
+ "chat_id": self.chat_id
59
+ }
60
+
61
+ if model:
62
+ payload["target_model_name"] = model
63
+
64
+ response = self._post_nd_json("/wxo-cpe/invoke", data=payload)
65
+
66
+ if response:
67
+ return response[-1]
@@ -16,22 +16,38 @@ from ibm_watsonx_orchestrate.cli.config import (
16
16
  from threading import Lock
17
17
  from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
18
18
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
19
+ from ibm_watsonx_orchestrate.cli.commands.channels.types import RuntimeEnvironmentType
19
20
  import logging
20
21
  from typing import TypeVar
21
22
  import os
22
23
  import jwt
23
24
  import time
25
+ import sys
24
26
 
25
27
  logger = logging.getLogger(__name__)
26
28
  LOCK = Lock()
27
29
  T = TypeVar("T", bound=BaseAPIClient)
28
30
 
31
+ def get_current_env_url() -> str:
32
+ cfg = Config()
33
+ active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
34
+ return cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
35
+
36
+ def get_cpd_instance_id_from_url(url: str | None = None) -> str:
37
+ if url is None:
38
+ url = get_current_env_url()
39
+
40
+ if not is_cpd_env(url):
41
+ logger.error(f"The host {url} is not a CPD instance")
42
+ sys.exit(1)
43
+
44
+ url_fragments = url.split('/')
45
+ return url_fragments[-1] if url_fragments[-1] else url_fragments[-2]
46
+
29
47
 
30
48
  def is_local_dev(url: str | None = None) -> bool:
31
49
  if url is None:
32
- cfg = Config()
33
- active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
34
- url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
50
+ url = get_current_env_url()
35
51
 
36
52
  if url.startswith("http://localhost"):
37
53
  return True
@@ -47,21 +63,45 @@ def is_local_dev(url: str | None = None) -> bool:
47
63
 
48
64
  return False
49
65
 
50
- def is_ibm_cloud():
51
- cfg = Config()
52
- active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
53
- url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
66
+ def is_cpd_env(url: str | None = None) -> bool:
67
+ if url is None:
68
+ url = get_current_env_url()
69
+
70
+ if url.lower().startswith("https://cpd"):
71
+ return True
72
+ return False
73
+
74
+ def is_saas_env():
75
+ return is_ga_platform() or is_ibm_cloud_platform()
76
+
77
+ def is_ibm_cloud_platform(url:str | None = None) -> bool:
78
+ if url is None:
79
+ url = get_current_env_url()
54
80
 
55
81
  if url.__contains__("cloud.ibm.com"):
56
82
  return True
57
83
  return False
58
84
 
85
+ def is_ga_platform(url: str | None = None) -> bool:
86
+ if url is None:
87
+ url = get_current_env_url()
59
88
 
60
- def is_cpd_env(url: str) -> bool:
61
- if url.lower().startswith("https://cpd"):
89
+ if url.__contains__("orchestrate.ibm.com"):
62
90
  return True
63
91
  return False
64
92
 
93
+
94
+ def get_environment() -> str:
95
+ if is_local_dev():
96
+ return RuntimeEnvironmentType.LOCAL
97
+ if is_cpd_env():
98
+ return RuntimeEnvironmentType.CPD
99
+ if is_ibm_cloud_platform():
100
+ return RuntimeEnvironmentType.IBM_CLOUD
101
+ if is_ga_platform():
102
+ return RuntimeEnvironmentType.AWS
103
+ return None
104
+
65
105
  def check_token_validity(token: str) -> bool:
66
106
  try:
67
107
  token_claimset = jwt.decode(token, options={"verify_signature": False})
@@ -46,7 +46,7 @@ services:
46
46
  WXO_SERVER_URL: http://wxo-server:4321
47
47
  MAX_POOL: 60
48
48
  DEPLOYMENT_MODE: laptop
49
- SUFFIXLIST: '["global_05d7ba72", "ibm_184bdbd3"]'
49
+ SUFFIXLIST: ${CM_SUFFIXLIST:-[]}
50
50
  ports:
51
51
  - 3001:3001
52
52
 
@@ -796,6 +796,23 @@ services:
796
796
  - 9044:9044
797
797
  depends_on:
798
798
  - wxo-server-db
799
+
800
+ cpe:
801
+ image: ${CPE_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-prompt-optimizer:${CPE_TAG:-latest}
802
+ platform: linux/amd64
803
+ restart: unless-stopped
804
+ environment:
805
+ WATSONX_APIKEY: ${WATSONX_APIKEY}
806
+ WATSONX_SPACE_ID: ${WATSONX_SPACE_ID}
807
+ WO_API_KEY: ${WO_API_KEY}
808
+ WO_USERNAME: ${WO_USERNAME}
809
+ WO_PASSWORD: ${WO_PASSWORD}
810
+ WO_INSTANCE: ${WO_INSTANCE}
811
+ USE_SAAS_ML_TOOLS_RUNTIME: ${USE_SAAS_ML_TOOLS_RUNTIME}
812
+ WO_AUTH_TYPE: ${WO_AUTH_TYPE}
813
+ AUTHORIZATION_URL: ${AUTHORIZATION_URL}
814
+ ports:
815
+ - 8081:8080
799
816
 
800
817
  ########################
801
818
  # DOCPROC dependencies
@@ -899,7 +916,7 @@ services:
899
916
  ENRICHMENT_BATCH_SIZE: "1000"
900
917
  CIPHER_AES_REALM_KEY: "dGVzdHRlc3R0ZXN0dGVzdA=="
901
918
  SIDECAR_METERED_ENABLED: "false"
902
- DPI_DEBUG: true
919
+ DPI_DEBUG: "false"
903
920
  DPI_WO_WDU_SERVER_ENDPOINT: https://wxo-doc-processing-service:8080
904
921
  # DPI_RAG_SERVER_ENDPOINT: https://wxo-doc-processing-llm-service:8083
905
922
  DISABLE_TLS: true
@@ -53,10 +53,10 @@ EVENT_BROKER_TTL="-1"
53
53
  REGISTRY_URL=
54
54
 
55
55
 
56
- SERVER_TAG=02-07-2025
56
+ SERVER_TAG=10-07-2025-beb40a3a
57
57
  SERVER_REGISTRY=
58
58
 
59
- WORKER_TAG=02-07-2025
59
+ WORKER_TAG=10-07-2025-beb40a3a
60
60
  WORKER_REGISTRY=
61
61
 
62
62
  AI_GATEWAY_TAG=01-07-2025
@@ -78,16 +78,16 @@ UITAG=27-06-2025
78
78
  CM_REGISTRY=
79
79
  CM_TAG=27-06-2025
80
80
 
81
- TRM_TAG=26-06-2025
81
+ TRM_TAG=08-07-2025
82
82
  TRM_REGISTRY=
83
83
 
84
- TR_TAG=26-06-2025
84
+ TR_TAG=08-07-2025
85
85
  TR_REGISTRY=
86
86
 
87
- BUILDER_TAG=02-07-2025
87
+ BUILDER_TAG=15-07-2025
88
88
  BUILDER_REGISTRY=
89
89
 
90
- FLOW_RUNTIME_TAG=04-07-2025
90
+ FLOW_RUNTIME_TAG=15-07-2025
91
91
  FLOW_RUMTIME_REGISTRY=
92
92
 
93
93
 
@@ -100,6 +100,9 @@ JAEGER_PROXY_REGISTRY=
100
100
  SOCKET_HANDLER_TAG=29-05-2025
101
101
  SOCKET_HANDLER_REGISTRY=
102
102
 
103
+ CPE_TAG=17-07-2025
104
+ CPE_REGISTRY=
105
+
103
106
  # IBM Document Processing
104
107
  WDU_TAG=2.5.0
105
108
  WDU_REGISTRY=
@@ -168,6 +171,7 @@ WO_INSTANCE=
168
171
  AUTHORIZATION_URL=
169
172
  WO_AUTH_TYPE=
170
173
  PYTHONPATH=
174
+ CM_SUFFIXLIST=
171
175
 
172
176
  # Use your machine's local IP address for external async tool communication.
173
177
  CALLBACK_HOST_URL=
@@ -1,6 +1,8 @@
1
1
  from .constants import START, END, RESERVED
2
- from ..types import FlowContext, TaskData, TaskEventType, DocumentContent
3
- from ..node import UserNode, AgentNode, StartNode, EndNode, PromptNode, ToolNode
2
+
3
+ from ..types import FlowContext, TaskData, TaskEventType, File, DecisionsCondition, DecisionsRule
4
+ from ..node import UserNode, AgentNode, StartNode, EndNode, PromptNode, ToolNode, DecisionsNode
5
+
4
6
  from .flow import Flow, CompiledFlow, FlowRun, FlowEvent, FlowEventType, FlowFactory, MatchPolicy, WaitPolicy, ForeachPolicy, Branch, Foreach, Loop
5
7
  from .decorators import flow
6
8
  from ..data_map import Assignment, DataMap
@@ -14,7 +16,7 @@ __all__ = [
14
16
  "FlowContext",
15
17
  "TaskData",
16
18
  "TaskEventType",
17
- "DocumentContent",
19
+ "File",
18
20
 
19
21
  "DocProcNode",
20
22
  "UserNode",
@@ -23,6 +25,7 @@ __all__ = [
23
25
  "EndNode",
24
26
  "PromptNode",
25
27
  "ToolNode",
28
+ "DecisionsNode",
26
29
  "Assignment",
27
30
  "DataMap",
28
31
 
@@ -38,8 +41,8 @@ __all__ = [
38
41
  "Branch",
39
42
  "Foreach",
40
43
  "Loop",
44
+ "DecisionsCondition",
45
+ "DecisionsRule",
41
46
 
42
- "user",
43
- "flow_spec",
44
47
  "flow"
45
48
  ]
@@ -27,12 +27,11 @@ from ibm_watsonx_orchestrate.client.utils import instantiate_client
27
27
  from ..types import (
28
28
  EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy, PromptLLMParameters, PromptNodeSpec,
29
29
  StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, UserFieldKind, UserFieldOption, UserFlowSpec, UserNodeSpec, WaitPolicy,
30
- DocProcSpec, TextExtractionResponse, KVPInvoicesExtractionResponse, KVPUtilityBillsExtractionResponse,
31
- DocumentContent
30
+ DocProcSpec, TextExtractionResponse, File, DecisionsNodeSpec, DecisionsRule
32
31
  )
33
32
  from .constants import CURRENT_USER, START, END, ANY_USER
34
33
  from ..node import (
35
- EndNode, Node, PromptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode, DocProcNode
34
+ EndNode, Node, PromptNode, StartNode, UserNode, AgentNode, DataMap, ToolNode, DocProcNode, DecisionsNode
36
35
  )
37
36
  from ..types import (
38
37
  AgentNodeSpec, extract_node_spec, FlowContext, FlowEventType, FlowEvent, FlowSpec,
@@ -434,6 +433,49 @@ class Flow(Node):
434
433
  node = self._add_node(node)
435
434
  return cast(PromptNode, node)
436
435
 
436
+ def decisions(self,
437
+ name: str,
438
+ display_name: str|None=None,
439
+ rules: list[DecisionsRule] | None = None,
440
+ default_actions: dict[str, Any] = None,
441
+ locale: str | None = None,
442
+ description: str | None = None,
443
+ input_schema: type[BaseModel]|None = None,
444
+ output_schema: type[BaseModel]|None=None,
445
+ input_map: DataMap = None) -> PromptNode:
446
+
447
+ if name is None:
448
+ raise ValueError("name must be provided.")
449
+
450
+ if rules is None:
451
+ raise ValueError("rules must be specified.")
452
+
453
+ # create input spec
454
+ input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = input_schema)
455
+ output_schema_obj = _get_json_schema_obj("output", output_schema)
456
+
457
+ # Create the tool spec
458
+ task_spec = DecisionsNodeSpec(
459
+ name=name,
460
+ display_name=display_name if display_name is not None else name,
461
+ description=description,
462
+ rules=rules,
463
+ default_actions=default_actions,
464
+ locale=locale,
465
+ input_schema=_get_tool_request_body(input_schema_obj),
466
+ output_schema=_get_tool_response_body(output_schema_obj),
467
+ output_schema_object = output_schema_obj
468
+ )
469
+
470
+ node = DecisionsNode(spec=task_spec)
471
+ # setup input map
472
+ if input_map:
473
+ node.input_map = self._get_data_map(input_map)
474
+
475
+ # add the node to the list of node
476
+ node = self._add_node(node)
477
+ return cast(DecisionsNode, node)
478
+
437
479
  def docproc(self,
438
480
  name: str,
439
481
  task: str,
@@ -448,12 +490,10 @@ class Flow(Node):
448
490
  raise ValueError("task must be provided.")
449
491
 
450
492
  output_schema_dict = {
451
- "text_extraction" : TextExtractionResponse,
452
- "kvp_invoices_extraction" : KVPInvoicesExtractionResponse,
453
- "kvp_utility_bills_extraction" : KVPUtilityBillsExtractionResponse
493
+ "text_extraction" : TextExtractionResponse
454
494
  }
455
495
  # create input spec
456
- input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = DocumentContent)
496
+ input_schema_obj = _get_json_schema_obj(parameter_name = "input", type_def = File)
457
497
  output_schema_obj = _get_json_schema_obj("output", output_schema_dict[task])
458
498
  if "$defs" in output_schema_obj.model_extra:
459
499
  output_schema_obj.model_extra.pop("$defs")
@@ -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, DocProcSpec
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):
@@ -116,7 +116,13 @@ class DocProcNode(Node):
116
116
 
117
117
  def get_spec(self) -> DocProcSpec:
118
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}')"
119
122
 
123
+ def get_spec(self) -> DecisionsNodeSpec:
124
+ return cast(DecisionsNodeSpec, self.spec)
125
+
120
126
  class NodeInstance(BaseModel):
121
127
  node: Node
122
128
  id: str # unique id of this task instance