ibm-watsonx-orchestrate 1.5.0b1__py3-none-any.whl → 1.6.0b0__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 (57) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
  3. ibm_watsonx_orchestrate/agent_builder/agents/types.py +53 -3
  4. ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
  5. ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
  6. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
  7. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
  8. ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -0
  9. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
  10. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +97 -3
  11. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +0 -1
  12. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +1 -1
  13. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
  14. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
  15. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
  16. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +224 -0
  17. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +158 -0
  18. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
  19. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
  20. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
  21. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
  22. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +25 -17
  23. ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
  24. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +0 -3
  25. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +14 -12
  26. ibm_watsonx_orchestrate/cli/config.py +2 -0
  27. ibm_watsonx_orchestrate/cli/main.py +6 -0
  28. ibm_watsonx_orchestrate/client/agents/agent_client.py +14 -8
  29. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
  30. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
  31. ibm_watsonx_orchestrate/client/base_api_client.py +9 -9
  32. ibm_watsonx_orchestrate/client/connections/connections_client.py +32 -6
  33. ibm_watsonx_orchestrate/client/connections/utils.py +1 -1
  34. ibm_watsonx_orchestrate/client/credentials.py +4 -0
  35. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
  36. ibm_watsonx_orchestrate/client/service_instance.py +42 -1
  37. ibm_watsonx_orchestrate/client/utils.py +27 -2
  38. ibm_watsonx_orchestrate/docker/compose-lite.yml +27 -17
  39. ibm_watsonx_orchestrate/docker/default.env +21 -15
  40. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
  41. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
  42. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
  43. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +70 -87
  44. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
  45. ibm_watsonx_orchestrate/flow_builder/utils.py +185 -0
  46. {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0b0.dist-info}/METADATA +2 -1
  47. {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0b0.dist-info}/RECORD +55 -53
  48. ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
  49. ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
  50. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
  51. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
  52. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
  53. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
  54. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
  55. {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0b0.dist-info}/WHEEL +0 -0
  56. {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0b0.dist-info}/entry_points.txt +0 -0
  57. {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -18,7 +18,7 @@ from ibm_watsonx_orchestrate.agent_builder.model_policies.types import ModelPoli
18
18
  ModelPolicyRetry, ModelPolicyStrategy, ModelPolicyStrategyMode, ModelPolicyTarget
19
19
  from ibm_watsonx_orchestrate.client.models.models_client import ModelsClient
20
20
  from ibm_watsonx_orchestrate.agent_builder.models.types import VirtualModel, ProviderConfig, ModelType, ANTHROPIC_DEFAULT_MAX_TOKENS
21
- from ibm_watsonx_orchestrate.client.utils import instantiate_client
21
+ from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_cpd_env
22
22
  from ibm_watsonx_orchestrate.client.connections import get_connection_id, ConnectionType
23
23
 
24
24
  logger = logging.getLogger(__name__)
@@ -85,6 +85,11 @@ def import_python_policy(file: str) -> List[ModelPolicy]:
85
85
  models.append(obj)
86
86
  return models
87
87
 
88
+ def validate_spec_content(content: dict) -> None:
89
+ if not content.get("spec_version"):
90
+ logger.error(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
91
+ sys.exit(1)
92
+
88
93
  def parse_model_file(file: str) -> List[VirtualModel]:
89
94
  if file.endswith('.yaml') or file.endswith('.yml') or file.endswith(".json"):
90
95
  with open(file, 'r') as f:
@@ -92,6 +97,7 @@ def parse_model_file(file: str) -> List[VirtualModel]:
92
97
  content = json.load(f)
93
98
  else:
94
99
  content = yaml.load(f, Loader=yaml.SafeLoader)
100
+ validate_spec_content(content)
95
101
  model = create_model_from_spec(spec=content)
96
102
  return [model]
97
103
  elif file.endswith('.py'):
@@ -107,6 +113,7 @@ def parse_policy_file(file: str) -> List[ModelPolicy]:
107
113
  content = json.load(f)
108
114
  else:
109
115
  content = yaml.load(f, Loader=yaml.SafeLoader)
116
+ validate_spec_content(content)
110
117
  policy = create_policy_from_spec(spec=content)
111
118
  return [policy]
112
119
  elif file.endswith('.py'):
@@ -160,13 +167,15 @@ class ModelsController:
160
167
  logger.error("Error: WATSONX_URL is required in the environment.")
161
168
  sys.exit(1)
162
169
 
163
- logger.info("Retrieving virtual-model models list...")
164
- virtual_models = models_client.list()
165
-
166
-
170
+ if is_cpd_env(models_client.base_url):
171
+ virtual_models = []
172
+ virtual_model_policies = []
173
+ else:
174
+ logger.info("Retrieving virtual-model models list...")
175
+ virtual_models = models_client.list()
167
176
 
168
- logger.info("Retrieving virtual-policies models list...")
169
- virtual_model_policies = model_policies_client.list()
177
+ logger.info("Retrieving virtual-policies models list...")
178
+ virtual_model_policies = model_policies_client.list()
170
179
 
171
180
  logger.info("Retrieving watsonx.ai models list...")
172
181
  found_models = _get_wxai_foundational_models()
@@ -382,7 +391,7 @@ class ModelsController:
382
391
  mode=strategy,
383
392
  on_status_codes=strategy_on_code
384
393
  )
385
- inner.targets = [ModelPolicyTarget(weight=1, model_name=m) for m in models]
394
+ inner.targets = [ModelPolicyTarget(model_name=m) for m in models]
386
395
  if retry_on_code:
387
396
  inner.retry = ModelPolicyRetry(
388
397
  on_status_codes=retry_on_code,
@@ -67,23 +67,28 @@ def docker_login(api_key: str, registry_url: str, username:str = "iamapikey") ->
67
67
  logger.info("Successfully logged in to Docker.")
68
68
 
69
69
  def docker_login_by_dev_edition_source(env_dict: dict, source: str) -> None:
70
- if not env_dict.get("REGISTRY_URL"):
71
- raise ValueError("REGISTRY_URL is not set.")
72
- registry_url = env_dict["REGISTRY_URL"].split("/")[0]
73
- if source == "internal":
74
- iam_api_key = env_dict.get("DOCKER_IAM_KEY")
75
- if not iam_api_key:
76
- raise ValueError("DOCKER_IAM_KEY is required in the environment file if WO_DEVELOPER_EDITION_SOURCE is set to 'internal'.")
77
- docker_login(iam_api_key, registry_url, "iamapikey")
78
- elif source == "myibm":
79
- wo_entitlement_key = env_dict.get("WO_ENTITLEMENT_KEY")
80
- if not wo_entitlement_key:
81
- raise ValueError("WO_ENTITLEMENT_KEY is required in the environment file.")
82
- docker_login(wo_entitlement_key, registry_url, "cp")
83
- elif source == "orchestrate":
84
- wo_auth_type = env_dict.get("WO_AUTH_TYPE")
85
- api_key, username = get_docker_cred_by_wo_auth_type(env_dict, wo_auth_type)
86
- docker_login(api_key, registry_url, username)
70
+ if env_dict.get('WO_DEVELOPER_EDITION_SKIP_LOGIN', None) == 'true':
71
+ logger.info('WO_DEVELOPER_EDITION_SKIP_LOGIN is set to true, skipping login.')
72
+ logger.warning('If the developer edition images are not already pulled this call will fail without first setting WO_DEVELOPER_EDITION_SKIP_LOGIN=false')
73
+ else:
74
+ if not env_dict.get("REGISTRY_URL"):
75
+ raise ValueError("REGISTRY_URL is not set.")
76
+ registry_url = env_dict["REGISTRY_URL"].split("/")[0]
77
+ if source == "internal":
78
+ iam_api_key = env_dict.get("DOCKER_IAM_KEY")
79
+ if not iam_api_key:
80
+ raise ValueError("DOCKER_IAM_KEY is required in the environment file if WO_DEVELOPER_EDITION_SOURCE is set to 'internal'.")
81
+ docker_login(iam_api_key, registry_url, "iamapikey")
82
+ elif source == "myibm":
83
+ wo_entitlement_key = env_dict.get("WO_ENTITLEMENT_KEY")
84
+ if not wo_entitlement_key:
85
+ raise ValueError("WO_ENTITLEMENT_KEY is required in the environment file.")
86
+ docker_login(wo_entitlement_key, registry_url, "cp")
87
+ elif source == "orchestrate":
88
+ wo_auth_type = env_dict.get("WO_AUTH_TYPE")
89
+ api_key, username = get_docker_cred_by_wo_auth_type(env_dict, wo_auth_type)
90
+ docker_login(api_key, registry_url, username)
91
+
87
92
 
88
93
  def get_compose_file() -> Path:
89
94
  with resources.as_file(
@@ -165,6 +170,8 @@ def get_docker_cred_by_wo_auth_type(env_dict: dict, auth_type: str | None) -> tu
165
170
  auth_type = "ibm_iam"
166
171
  elif ".ibm.com" in instance_url:
167
172
  auth_type = "mcsp"
173
+ elif "https://cpd" in instance_url:
174
+ auth_type = "cpd"
168
175
 
169
176
  if auth_type in {"mcsp", "ibm_iam"}:
170
177
  wo_api_key = env_dict.get("WO_API_KEY")
@@ -672,6 +679,7 @@ def server_start(
672
679
  if experimental_with_langfuse:
673
680
  merged_env_dict['LANGFUSE_ENABLED'] = 'true'
674
681
 
682
+
675
683
  try:
676
684
  docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
677
685
  except ValueError as e:
@@ -26,7 +26,8 @@ def _infer_auth_type_from_instance_url(instance_url: str) -> WoAuthType:
26
26
  return WoAuthType.IBM_IAM
27
27
  if ".ibm.com" in instance_url:
28
28
  return WoAuthType.MCSP
29
- return WoAuthType.CPD
29
+ if "https://cpd" in instance_url:
30
+ return WoAuthType.CPD
30
31
 
31
32
 
32
33
  class WatsonXAIEnvConfig(BaseModel):
@@ -213,9 +213,6 @@ class ToolkitController:
213
213
 
214
214
 
215
215
  def remove_toolkit(self, name: str):
216
- if not is_local_dev():
217
- logger.error("This functionality is only available for Local Environments")
218
- sys.exit(1)
219
216
  try:
220
217
  client = self.get_client()
221
218
  draft_toolkits = client.get_draft_by_name(toolkit_name=name)
@@ -33,7 +33,7 @@ from ibm_watsonx_orchestrate.cli.config import Config, CONTEXT_SECTION_HEADER, C
33
33
  PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, \
34
34
  DEFAULT_CONFIG_FILE_CONTENT
35
35
  from ibm_watsonx_orchestrate.agent_builder.connections import ConnectionSecurityScheme, ExpectedCredentials
36
- from ibm_watsonx_orchestrate.experimental.flow_builder.flows.decorators import FlowWrapper
36
+ from ibm_watsonx_orchestrate.flow_builder.flows.decorators import FlowWrapper
37
37
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
38
38
  from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
39
39
  from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
@@ -41,7 +41,7 @@ from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_de
41
41
  from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
42
42
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
43
43
  from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
44
- from ibm_watsonx_orchestrate.experimental.flow_builder.utils import import_flow_model
44
+ from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_model
45
45
 
46
46
  from ibm_watsonx_orchestrate import __version__
47
47
 
@@ -339,14 +339,13 @@ async def import_flow_tool(file: str) -> None:
339
339
  theme = rich.theme.Theme({"model.name": "bold cyan"})
340
340
  console = rich.console.Console(highlighter=ModelHighlighter(), theme=theme)
341
341
 
342
- message = f"""[bold cyan]Flow Tools: Experimental Feature[/bold cyan]
342
+ message = f"""[bold cyan]Flow Tools[/bold cyan]
343
343
 
344
344
  The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
345
345
 
346
346
  [bold cyan]Additional information:[/bold cyan]
347
347
 
348
- - Ensure the flow engine is running by issuing the [bold cyan]orchestrate server start[/bold cyan] command with the [bold cyan]--with-flow-runtime[/bold cyan] option
349
- - The [bold green]get_flow_status[/bold green] tool is being imported to support flow tools. Ensure [bold]both this tools and the one you are importing are added to your agent[/bold] to retrieve the flow output.
348
+ - The [bold green]get_flow_status[/bold green] tool is being imported to support flow tools. To get a flow's current status, ensure [bold]both this tools and the one you are importing are added to your agent[/bold] to retrieve the flow output.
350
349
  - Include additional instructions in your agent to call the [bold green]get_flow_status[/bold green] tool to retrieve the flow output. For example: [green]"If you get an instance_id, use the tool get_flow_status to retrieve the current status of a flow."[/green]
351
350
 
352
351
  """
@@ -438,6 +437,10 @@ def _get_kind_from_spec(spec: dict) -> ToolKind:
438
437
  return ToolKind.python
439
438
  elif ToolKind.openapi in tool_binding:
440
439
  return ToolKind.openapi
440
+ elif ToolKind.mcp in tool_binding:
441
+ return ToolKind.mcp
442
+ elif 'wxflows' in tool_binding:
443
+ return ToolKind.flow
441
444
  else:
442
445
  logger.error(f"Could not determine 'kind' of tool '{name}'")
443
446
  sys.exit(1)
@@ -560,14 +563,13 @@ class ToolsController:
560
563
 
561
564
  toolkit_name = ""
562
565
 
563
- if is_local_dev():
566
+ if tool.__tool_spec__.toolkit_id:
564
567
  toolkit_client = instantiate_client(ToolKitClient)
565
- if tool.__tool_spec__.toolkit_id:
566
- toolkit = toolkit_client.get_draft_by_id(tool.__tool_spec__.toolkit_id)
567
- if isinstance(toolkit, dict) and "name" in toolkit:
568
- toolkit_name = toolkit["name"]
569
- elif toolkit:
570
- toolkit_name = str(toolkit)
568
+ toolkit = toolkit_client.get_draft_by_id(tool.__tool_spec__.toolkit_id)
569
+ if isinstance(toolkit, dict) and "name" in toolkit:
570
+ toolkit_name = toolkit["name"]
571
+ elif toolkit:
572
+ toolkit_name = str(toolkit)
571
573
 
572
574
 
573
575
  table.add_row(
@@ -26,6 +26,8 @@ ENV_WXO_URL_OPT = "wxo_url"
26
26
  ENV_IAM_URL_OPT = "iam_url"
27
27
  PROTECTED_ENV_NAME = "local"
28
28
  ENV_AUTH_TYPE = "auth_type"
29
+ BYPASS_SSL = "bypass_ssl"
30
+ VERIFY = "verify"
29
31
  ENV_ACCEPT_LICENSE = 'accepts_license_agreements'
30
32
 
31
33
  DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
@@ -12,8 +12,13 @@ from ibm_watsonx_orchestrate.cli.commands.environment.environment_command import
12
12
  from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import channel_app
13
13
  from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
14
14
  from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
15
+ from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_command import evaluation_app
15
16
  from ibm_watsonx_orchestrate.cli.init_helper import init_callback
16
17
 
18
+ import urllib3
19
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20
+
21
+
17
22
  app = typer.Typer(
18
23
  no_args_is_help=True,
19
24
  pretty_exceptions_enable=False,
@@ -30,6 +35,7 @@ app.add_typer(server_app, name="server", help='Manipulate your local Orchestrate
30
35
  app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Developer Edition server [requires entitlement]')
31
36
  app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
32
37
  app.add_typer(channel_app, name="channels", help="Configure channels where your agent can exist on (such as embedded webchat)")
38
+ app.add_typer(evaluation_app, name="evaluations", help='Evaluate the performance of your agents in your active env')
33
39
  app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
34
40
 
35
41
  if __name__ == "__main__":
@@ -1,7 +1,11 @@
1
1
  from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
2
- from typing_extensions import List
2
+ from typing_extensions import List, Optional
3
3
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
4
+ from pydantic import BaseModel
4
5
 
6
+ class AgentUpsertResponse(BaseModel):
7
+ id: Optional[str] = None
8
+ warning: Optional[str] = None
5
9
 
6
10
  class AgentClient(BaseAPIClient):
7
11
  """
@@ -12,14 +16,16 @@ class AgentClient(BaseAPIClient):
12
16
  self.base_endpoint = "/orchestrate/agents" if is_local_dev(self.base_url) else "/agents"
13
17
 
14
18
 
15
- def create(self, payload: dict) -> dict:
16
- return self._post(self.base_endpoint, data=payload)
19
+ def create(self, payload: dict) -> AgentUpsertResponse:
20
+ response = self._post(self.base_endpoint, data=payload)
21
+ return AgentUpsertResponse.model_validate(response)
17
22
 
18
23
  def get(self) -> dict:
19
- return self._get(self.base_endpoint)
24
+ return self._get(f"{self.base_endpoint}?include_hidden=true")
20
25
 
21
- def update(self, agent_id: str, data: dict) -> dict:
22
- return self._patch(f"{self.base_endpoint}/{agent_id}", data=data)
26
+ def update(self, agent_id: str, data: dict) -> AgentUpsertResponse:
27
+ response = self._patch(f"{self.base_endpoint}/{agent_id}", data=data)
28
+ return AgentUpsertResponse.model_validate(response)
23
29
 
24
30
  def delete(self, agent_id: str) -> dict:
25
31
  return self._delete(f"{self.base_endpoint}/{agent_id}")
@@ -29,7 +35,7 @@ class AgentClient(BaseAPIClient):
29
35
 
30
36
  def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
31
37
  formatted_agent_names = [f"names={x}" for x in agent_names]
32
- return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}")
38
+ return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true")
33
39
 
34
40
  def get_draft_by_id(self, agent_id: str) -> List[dict]:
35
41
  if agent_id is None:
@@ -45,5 +51,5 @@ class AgentClient(BaseAPIClient):
45
51
 
46
52
  def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
47
53
  formatted_agent_ids = [f"ids={x}" for x in agent_ids]
48
- return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}")
54
+ return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true")
49
55
 
@@ -10,7 +10,7 @@ class AssistantAgentClient(BaseAPIClient):
10
10
  return self._post("/assistants/watsonx", data=payload)
11
11
 
12
12
  def get(self) -> dict:
13
- return self._get("/assistants/watsonx")
13
+ return self._get("/assistants/watsonx?include_hidden=true")
14
14
 
15
15
  def update(self, agent_id: str, data: dict) -> dict:
16
16
  return self._patch(f"/assistants/watsonx/{agent_id}", data=data)
@@ -23,7 +23,7 @@ class AssistantAgentClient(BaseAPIClient):
23
23
 
24
24
  def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
25
25
  formatted_agent_names = [f"names={x}" for x in agent_names]
26
- return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_names)}")
26
+ return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_names)}&include_hidden=true")
27
27
 
28
28
  def get_draft_by_id(self, agent_id: str) -> dict | str:
29
29
  if agent_id is None:
@@ -39,4 +39,4 @@ class AssistantAgentClient(BaseAPIClient):
39
39
 
40
40
  def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
41
41
  formatted_agent_ids = [f"ids={x}" for x in agent_ids]
42
- return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_ids)}")
42
+ return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_ids)}&include_hidden=true")
@@ -10,7 +10,7 @@ class ExternalAgentClient(BaseAPIClient):
10
10
  return self._post("/agents/external-chat", data=payload)
11
11
 
12
12
  def get(self) -> dict:
13
- return self._get("/agents/external-chat")
13
+ return self._get("/agents/external-chat?include_hidden=true")
14
14
 
15
15
  def update(self, agent_id: str, data: dict) -> dict:
16
16
  return self._patch(f"/agents/external-chat/{agent_id}", data=data)
@@ -39,4 +39,4 @@ class ExternalAgentClient(BaseAPIClient):
39
39
 
40
40
  def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
41
41
  formatted_agent_ids = [f"ids={x}" for x in agent_ids]
42
- return self._get(f"/agents/external-chat?{'&'.join(formatted_agent_ids)}")
42
+ return self._get(f"/agents/external-chat?{'&'.join(formatted_agent_ids)}&include_hidden=true")
@@ -24,8 +24,7 @@ class ClientAPIException(requests.HTTPError):
24
24
 
25
25
 
26
26
  class BaseAPIClient:
27
-
28
- def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, authenticator: MCSPAuthenticator = None):
27
+ def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, verify: str = None, authenticator: MCSPAuthenticator = None):
29
28
  self.base_url = base_url.rstrip("/") # remove trailing slash
30
29
  self.api_key = api_key
31
30
  self.authenticator = authenticator
@@ -33,6 +32,7 @@ class BaseAPIClient:
33
32
  # api path can be re-written by api proxy when deployed
34
33
  # TO-DO: re-visit this when shipping to production
35
34
  self.is_local = is_local
35
+ self.verify = verify
36
36
 
37
37
  if not self.is_local:
38
38
  self.base_url = f"{self.base_url}/v1/orchestrate"
@@ -48,7 +48,7 @@ class BaseAPIClient:
48
48
  def _get(self, path: str, params: dict = None, data=None, return_raw=False) -> dict:
49
49
 
50
50
  url = f"{self.base_url}{path}"
51
- response = requests.get(url, headers=self._get_headers(), params=params, data=data)
51
+ response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
52
52
  self._check_response(response)
53
53
  if not return_raw:
54
54
  return response.json()
@@ -57,39 +57,39 @@ class BaseAPIClient:
57
57
 
58
58
  def _post(self, path: str, data: dict = None, files: dict = None) -> dict:
59
59
  url = f"{self.base_url}{path}"
60
- response = requests.post(url, headers=self._get_headers(), json=data, files=files)
60
+ response = requests.post(url, headers=self._get_headers(), json=data, files=files, verify=self.verify)
61
61
  self._check_response(response)
62
62
  return response.json() if response.text else {}
63
63
 
64
64
  def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
65
65
  url = f"{self.base_url}{path}"
66
66
  # Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
67
- response = requests.post(url, headers=self._get_headers(), data=data, files=files)
67
+ response = requests.post(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
68
68
  self._check_response(response)
69
69
  return response.json() if response.text else {}
70
70
 
71
71
  def _put(self, path: str, data: dict = None) -> dict:
72
72
 
73
73
  url = f"{self.base_url}{path}"
74
- response = requests.put(url, headers=self._get_headers(), json=data)
74
+ response = requests.put(url, headers=self._get_headers(), json=data, verify=self.verify)
75
75
  self._check_response(response)
76
76
  return response.json() if response.text else {}
77
77
 
78
78
  def _patch(self, path: str, data: dict = None) -> dict:
79
79
  url = f"{self.base_url}{path}"
80
- response = requests.patch(url, headers=self._get_headers(), json=data)
80
+ response = requests.patch(url, headers=self._get_headers(), json=data, verify=self.verify)
81
81
  self._check_response(response)
82
82
  return response.json() if response.text else {}
83
83
 
84
84
  def _patch_form_data(self, path: str, data: dict = None, files = None) -> dict:
85
85
  url = f"{self.base_url}{path}"
86
- response = requests.patch(url, headers=self._get_headers(), data=data, files=files)
86
+ response = requests.patch(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
87
87
  self._check_response(response)
88
88
  return response.json() if response.text else {}
89
89
 
90
90
  def _delete(self, path: str, data=None) -> dict:
91
91
  url = f"{self.base_url}{path}"
92
- response = requests.delete(url, headers=self._get_headers(), json=data)
92
+ response = requests.delete(url, headers=self._get_headers(), json=data, verify=self.verify)
93
93
  self._check_response(response)
94
94
  return response.json() if response.text else {}
95
95
 
@@ -5,6 +5,7 @@ from typing import Optional
5
5
 
6
6
  from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
7
7
  from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
8
+ from ibm_watsonx_orchestrate.client.utils import is_cpd_env
8
9
 
9
10
  import logging
10
11
  logger = logging.getLogger(__name__)
@@ -53,9 +54,14 @@ class ConnectionsClient(BaseAPIClient):
53
54
  return self._delete(f"/connections/applications/{app_id}")
54
55
 
55
56
  # GET /api/v1/connections/applications/{app_id}
56
- def get(self, app_id: str) -> GetConnectionResponse:
57
+ def get(self, app_id: str) -> GetConnectionResponse | None:
57
58
  try:
58
- return GetConnectionResponse.model_validate(self._get(f"/connections/applications/{app_id}"))
59
+ path = (
60
+ f"/connections/applications/{app_id}"
61
+ if is_cpd_env(self.base_url)
62
+ else f"/connections/applications?app_id={app_id}"
63
+ )
64
+ return GetConnectionResponse.model_validate(self._get(path))
59
65
  except ClientAPIException as e:
60
66
  if e.response.status_code == 404:
61
67
  return None
@@ -65,7 +71,12 @@ class ConnectionsClient(BaseAPIClient):
65
71
  # GET api/v1/connections/applications
66
72
  def list(self) -> List[ListConfigsResponse]:
67
73
  try:
68
- res = self._get(f"/connections/applications")
74
+ path = (
75
+ f"/connections/applications"
76
+ if is_cpd_env(self.base_url)
77
+ else f"/connections/applications?include_details=true"
78
+ )
79
+ res = self._get(path)
69
80
  return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
70
81
  except ValidationError as e:
71
82
  logger.error("Recieved unexpected response from server")
@@ -115,9 +126,19 @@ class ConnectionsClient(BaseAPIClient):
115
126
  def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_sso: bool) -> dict:
116
127
  try:
117
128
  if use_sso:
118
- return self._get(f"/connections/applications/{app_id}/credentials?env={env}")
129
+ path = (
130
+ f"/connections/applications/{app_id}/credentials?env={env}"
131
+ if is_cpd_env(self.base_url)
132
+ else f"/connections/applications/{app_id}/credentials/{env}"
133
+ )
134
+ return self._get(path)
119
135
  else:
120
- return self._get(f"/connections/applications/{app_id}/configs/runtime_credentials?env={env}")
136
+ path = (
137
+ f"/connections/applications/{app_id}/configs/runtime_credentials?env={env}"
138
+ if is_cpd_env(self.base_url)
139
+ else f"/connections/applications/runtime_credentials?app_id={app_id}&env={env}"
140
+ )
141
+ return self._get(path)
121
142
  except ClientAPIException as e:
122
143
  if e.response.status_code == 404:
123
144
  return None
@@ -147,7 +168,12 @@ class ConnectionsClient(BaseAPIClient):
147
168
  if conn_id is None:
148
169
  return ""
149
170
  try:
150
- app_details = self._get(f"/connections/applications/id/{conn_id}")
171
+ path = (
172
+ f"/connections/applications/id/{conn_id}"
173
+ if is_cpd_env(self.base_url)
174
+ else f"/connections/applications?connection_id={conn_id}"
175
+ )
176
+ app_details = self._get(path)
151
177
  return app_details.get("app_id")
152
178
  except ClientAPIException as e:
153
179
  if e.response.status_code == 404:
@@ -17,7 +17,7 @@ def _get_connections_manager_url() -> str:
17
17
  url_parts = url.split(":")
18
18
  url_parts[-1] = str(LOCAL_CONNECTION_MANAGER_PORT)
19
19
  url = ":".join(url_parts)
20
- url = url + "/api/v1"
20
+ url = url + "/api/v1/orchestrate"
21
21
  return url
22
22
  return None
23
23
 
@@ -31,6 +31,8 @@ class Credentials:
31
31
  url: str | None = None,
32
32
  iam_url: str | None = None,
33
33
  api_key: str | None = None,
34
+ username: str | None = None,
35
+ password: str | None = None,
34
36
  token: str | None = None,
35
37
  verify: str | bool | None = None,
36
38
  auth_type: str | None = None,
@@ -39,6 +41,8 @@ class Credentials:
39
41
  self.url = url
40
42
  self.iam_url = iam_url
41
43
  self.api_key = api_key
44
+ self.username = username
45
+ self.password = password
42
46
  self.token = token
43
47
  self.local_global_token = None
44
48
  self.verify = verify
@@ -17,11 +17,11 @@ class ModelPoliciesClient(BaseAPIClient):
17
17
  """
18
18
  # POST api/v1/model_policy
19
19
  def create(self, model: ModelPolicy) -> None:
20
- self._post("/model_policy", data=model.model_dump())
20
+ self._post("/model_policy", data=model.model_dump(exclude_none=True))
21
21
 
22
22
  # PUT api/v1/model_policy/{model_policy_id}
23
23
  def update(self, model_policy_id: str, model: ModelPolicy) -> None:
24
- self._put(f"/model_policy/{model_policy_id}", data=model.model_dump())
24
+ self._put(f"/model_policy/{model_policy_id}", data=model.model_dump(exclude_none=True))
25
25
 
26
26
  # DELETE api/v1/model_policy/{model_policy_id}
27
27
  def delete(self, model_policy_id: str) -> dict:
@@ -7,8 +7,9 @@ from __future__ import annotations
7
7
 
8
8
  from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
9
9
  from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
10
+ from ibm_cloud_sdk_core.authenticators import CloudPakForDataAuthenticator
10
11
 
11
- from ibm_watsonx_orchestrate.client.utils import check_token_validity
12
+ from ibm_watsonx_orchestrate.client.utils import check_token_validity, is_cpd_env
12
13
  from ibm_watsonx_orchestrate.client.base_service_instance import BaseServiceInstance
13
14
  from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
14
15
 
@@ -16,6 +17,16 @@ from ibm_watsonx_orchestrate.client.client_errors import (
16
17
  ClientError,
17
18
  )
18
19
 
20
+ import logging
21
+ logger = logging.getLogger(__name__)
22
+
23
+ from ibm_watsonx_orchestrate.cli.config import (
24
+ Config,
25
+ CONTEXT_SECTION_HEADER,
26
+ CONTEXT_ACTIVE_ENV_OPT,
27
+ ENVIRONMENTS_SECTION_HEADER,
28
+ ENV_WXO_URL_OPT
29
+ )
19
30
 
20
31
  class ServiceInstance(BaseServiceInstance):
21
32
  """Connect, get details, and check usage of a Watson Machine Learning service instance."""
@@ -40,8 +51,13 @@ class ServiceInstance(BaseServiceInstance):
40
51
  def _create_token(self) -> str:
41
52
  if not self._credentials.auth_type:
42
53
  if ".cloud.ibm.com" in self._credentials.url:
54
+ logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mscp' or 'cpd")
43
55
  return self._authenticate(EnvironmentAuthType.IBM_CLOUD_IAM)
56
+ elif is_cpd_env(self._credentials.url):
57
+ logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mscp' or 'cpd")
58
+ return self._authenticate(EnvironmentAuthType.CPD)
44
59
  else:
60
+ logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mscp' or 'cpd' ")
45
61
  return self._authenticate(EnvironmentAuthType.MCSP)
46
62
  else:
47
63
  return self._authenticate(self._credentials.auth_type)
@@ -55,6 +71,31 @@ class ServiceInstance(BaseServiceInstance):
55
71
  authenticator = MCSPAuthenticator(apikey=self._credentials.api_key, url=url)
56
72
  case EnvironmentAuthType.IBM_CLOUD_IAM:
57
73
  authenticator = IAMAuthenticator(apikey=self._credentials.api_key, url=self._credentials.iam_url)
74
+ case EnvironmentAuthType.CPD:
75
+ url = ""
76
+ if self._credentials.iam_url is not None:
77
+ url = self._credentials.iam_url
78
+ else:
79
+ cfg = Config()
80
+ env_cfg = cfg.get(ENVIRONMENTS_SECTION_HEADER)
81
+ matching_wxo_url = next(
82
+ (env_config['wxo_url'] for env_config in env_cfg.values() if 'bypass_ssl' in env_config and 'verify' in env_config),
83
+ None
84
+ )
85
+ base_url = matching_wxo_url.split("/orchestrate")[0]
86
+ url = f"{base_url}/icp4d-api"
87
+
88
+ password = self._credentials.password if self._credentials.password is not None else None
89
+ api_key = self._credentials.api_key if self._credentials.api_key is not None else None
90
+ cpd_password=password if password else None
91
+ cpd_apikey=api_key if api_key else None
92
+ authenticator = CloudPakForDataAuthenticator(
93
+ username=self._credentials.username,
94
+ password=cpd_password,
95
+ apikey=cpd_apikey,
96
+ url=url,
97
+ disable_ssl_verification=True
98
+ )
58
99
  case _:
59
100
  raise ClientError(f"Unsupported authentication type: {auth_type}")
60
101