ibm-watsonx-orchestrate 1.5.0b1__py3-none-any.whl → 1.6.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.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
- ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
- ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
- ibm_watsonx_orchestrate/cli/config.py +2 -0
- ibm_watsonx_orchestrate/cli/main.py +6 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
- ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/credentials.py +4 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
- ibm_watsonx_orchestrate/client/service_instance.py +42 -1
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +37 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
- ibm_watsonx_orchestrate/docker/default.env +40 -15
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
- ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
- {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
- {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.5.0b1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,79 @@
|
|
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
|
+
def transform_agents_from_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
|
7
|
+
if isinstance(agents,list):
|
8
|
+
new_agents = []
|
9
|
+
for agent in agents:
|
10
|
+
new_agents.append(_transform_agent_from_flat_agent_spec(agent))
|
11
|
+
agents = new_agents
|
12
|
+
else:
|
13
|
+
agents = _transform_agent_from_flat_agent_spec(agents)
|
14
|
+
|
15
|
+
return agents
|
16
|
+
|
17
|
+
|
18
|
+
def _transform_agent_from_flat_agent_spec(agent_spec: dict ) -> dict:
|
19
|
+
transformed = {"additional_properties": {}}
|
20
|
+
for key,value in agent_spec.items():
|
21
|
+
if key == "starter_prompts":
|
22
|
+
if value:
|
23
|
+
value.pop("is_default_prompts",None)
|
24
|
+
value["customize"] = value.pop("prompts", [])
|
25
|
+
|
26
|
+
transformed["additional_properties"] |= { key: value }
|
27
|
+
|
28
|
+
elif key == "welcome_content":
|
29
|
+
if value:
|
30
|
+
value.pop("is_default_message", None)
|
31
|
+
|
32
|
+
transformed["additional_properties"] |= { key: value }
|
33
|
+
|
34
|
+
else:
|
35
|
+
transformed |= { key: value }
|
36
|
+
|
37
|
+
return transformed
|
38
|
+
|
39
|
+
def transform_agents_to_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
|
40
|
+
if isinstance(agents,list):
|
41
|
+
new_agents = []
|
42
|
+
for agent in agents:
|
43
|
+
new_agents.append(_transform_agent_to_flat_agent_spec(agent))
|
44
|
+
agents = new_agents
|
45
|
+
else:
|
46
|
+
agents = _transform_agent_to_flat_agent_spec(agents)
|
47
|
+
|
48
|
+
return agents
|
49
|
+
|
50
|
+
def _transform_agent_to_flat_agent_spec(agent_spec: dict ) -> dict:
|
51
|
+
additional_properties = agent_spec.get("additional_properties", None)
|
52
|
+
if not additional_properties:
|
53
|
+
return agent_spec
|
54
|
+
|
55
|
+
transformed = agent_spec
|
56
|
+
for key,value in additional_properties.items():
|
57
|
+
if key == "starter_prompts":
|
58
|
+
if value:
|
59
|
+
value["is_default_prompts"] = False
|
60
|
+
value["prompts"] = value.pop("customize", [])
|
61
|
+
|
62
|
+
transformed[key] = value
|
63
|
+
|
64
|
+
elif key == "welcome_content":
|
65
|
+
if value:
|
66
|
+
value["is_default_message"] = False
|
67
|
+
|
68
|
+
transformed[key] = value
|
69
|
+
|
70
|
+
transformed.pop("additional_properties",None)
|
71
|
+
|
72
|
+
return transformed
|
73
|
+
|
74
|
+
class AgentUpsertResponse(BaseModel):
|
75
|
+
id: Optional[str] = None
|
76
|
+
warning: Optional[str] = None
|
5
77
|
|
6
78
|
class AgentClient(BaseAPIClient):
|
7
79
|
"""
|
@@ -12,14 +84,16 @@ class AgentClient(BaseAPIClient):
|
|
12
84
|
self.base_endpoint = "/orchestrate/agents" if is_local_dev(self.base_url) else "/agents"
|
13
85
|
|
14
86
|
|
15
|
-
def create(self, payload: dict) ->
|
16
|
-
|
87
|
+
def create(self, payload: dict) -> AgentUpsertResponse:
|
88
|
+
response = self._post(self.base_endpoint, data=transform_agents_from_flat_agent_spec(payload))
|
89
|
+
return AgentUpsertResponse.model_validate(response)
|
17
90
|
|
18
91
|
def get(self) -> dict:
|
19
|
-
return self._get(self.base_endpoint)
|
92
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?include_hidden=true"))
|
20
93
|
|
21
|
-
def update(self, agent_id: str, data: dict) ->
|
22
|
-
|
94
|
+
def update(self, agent_id: str, data: dict) -> AgentUpsertResponse:
|
95
|
+
response = self._patch(f"{self.base_endpoint}/{agent_id}", data=transform_agents_from_flat_agent_spec(data))
|
96
|
+
return AgentUpsertResponse.model_validate(response)
|
23
97
|
|
24
98
|
def delete(self, agent_id: str) -> dict:
|
25
99
|
return self._delete(f"{self.base_endpoint}/{agent_id}")
|
@@ -29,14 +103,14 @@ class AgentClient(BaseAPIClient):
|
|
29
103
|
|
30
104
|
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
31
105
|
formatted_agent_names = [f"names={x}" for x in agent_names]
|
32
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}")
|
106
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true"))
|
33
107
|
|
34
108
|
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
35
109
|
if agent_id is None:
|
36
110
|
return ""
|
37
111
|
else:
|
38
112
|
try:
|
39
|
-
agent = self._get(f"{self.base_endpoint}/{agent_id}")
|
113
|
+
agent = transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}/{agent_id}"))
|
40
114
|
return agent
|
41
115
|
except ClientAPIException as e:
|
42
116
|
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
@@ -45,5 +119,5 @@ class AgentClient(BaseAPIClient):
|
|
45
119
|
|
46
120
|
def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
|
47
121
|
formatted_agent_ids = [f"ids={x}" for x in agent_ids]
|
48
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}")
|
122
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true"))
|
49
123
|
|
@@ -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,9 +32,12 @@ 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"
|
39
|
+
else:
|
40
|
+
self.base_url = f"{self.base_url}/v1"
|
39
41
|
|
40
42
|
def _get_headers(self) -> dict:
|
41
43
|
headers = {}
|
@@ -46,9 +48,8 @@ class BaseAPIClient:
|
|
46
48
|
return headers
|
47
49
|
|
48
50
|
def _get(self, path: str, params: dict = None, data=None, return_raw=False) -> dict:
|
49
|
-
|
50
51
|
url = f"{self.base_url}{path}"
|
51
|
-
response = requests.get(url, headers=self._get_headers(), params=params, data=data)
|
52
|
+
response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
|
52
53
|
self._check_response(response)
|
53
54
|
if not return_raw:
|
54
55
|
return response.json()
|
@@ -57,39 +58,39 @@ class BaseAPIClient:
|
|
57
58
|
|
58
59
|
def _post(self, path: str, data: dict = None, files: dict = None) -> dict:
|
59
60
|
url = f"{self.base_url}{path}"
|
60
|
-
response = requests.post(url, headers=self._get_headers(), json=data, files=files)
|
61
|
+
response = requests.post(url, headers=self._get_headers(), json=data, files=files, verify=self.verify)
|
61
62
|
self._check_response(response)
|
62
63
|
return response.json() if response.text else {}
|
63
64
|
|
64
65
|
def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
|
65
66
|
url = f"{self.base_url}{path}"
|
66
67
|
# 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)
|
68
|
+
response = requests.post(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
|
68
69
|
self._check_response(response)
|
69
70
|
return response.json() if response.text else {}
|
70
71
|
|
71
72
|
def _put(self, path: str, data: dict = None) -> dict:
|
72
73
|
|
73
74
|
url = f"{self.base_url}{path}"
|
74
|
-
response = requests.put(url, headers=self._get_headers(), json=data)
|
75
|
+
response = requests.put(url, headers=self._get_headers(), json=data, verify=self.verify)
|
75
76
|
self._check_response(response)
|
76
77
|
return response.json() if response.text else {}
|
77
78
|
|
78
79
|
def _patch(self, path: str, data: dict = None) -> dict:
|
79
80
|
url = f"{self.base_url}{path}"
|
80
|
-
response = requests.patch(url, headers=self._get_headers(), json=data)
|
81
|
+
response = requests.patch(url, headers=self._get_headers(), json=data, verify=self.verify)
|
81
82
|
self._check_response(response)
|
82
83
|
return response.json() if response.text else {}
|
83
84
|
|
84
85
|
def _patch_form_data(self, path: str, data: dict = None, files = None) -> dict:
|
85
86
|
url = f"{self.base_url}{path}"
|
86
|
-
response = requests.patch(url, headers=self._get_headers(), data=data, files=files)
|
87
|
+
response = requests.patch(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
|
87
88
|
self._check_response(response)
|
88
89
|
return response.json() if response.text else {}
|
89
90
|
|
90
91
|
def _delete(self, path: str, data=None) -> dict:
|
91
92
|
url = f"{self.base_url}{path}"
|
92
|
-
response = requests.delete(url, headers=self._get_headers(), json=data)
|
93
|
+
response = requests.delete(url, headers=self._get_headers(), json=data, verify=self.verify)
|
93
94
|
self._check_response(response)
|
94
95
|
return response.json() if response.text else {}
|
95
96
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from typing import List
|
2
2
|
|
3
|
+
from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
|
3
4
|
from pydantic import BaseModel, ValidationError
|
4
5
|
from typing import Optional
|
5
6
|
|
6
7
|
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
7
8
|
from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
|
9
|
+
from ibm_watsonx_orchestrate.client.utils import is_cpd_env, is_local_dev
|
8
10
|
|
9
11
|
import logging
|
10
12
|
logger = logging.getLogger(__name__)
|
@@ -41,6 +43,12 @@ class GetConnectionResponse(BaseModel):
|
|
41
43
|
|
42
44
|
|
43
45
|
class ConnectionsClient(BaseAPIClient):
|
46
|
+
def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, verify: str = None, authenticator: MCSPAuthenticator = None):
|
47
|
+
super(ConnectionsClient, self).__init__(base_url, api_key, is_local, verify, authenticator)
|
48
|
+
if is_local_dev(base_url):
|
49
|
+
self.base_url = f"{base_url.rstrip('/')}/api/v1/orchestrate"
|
50
|
+
else:
|
51
|
+
self.base_url = f"{base_url.rstrip('/')}/v1/orchestrate"
|
44
52
|
"""
|
45
53
|
Client to handle CRUD operations for Connections endpoint
|
46
54
|
"""
|
@@ -53,9 +61,14 @@ class ConnectionsClient(BaseAPIClient):
|
|
53
61
|
return self._delete(f"/connections/applications/{app_id}")
|
54
62
|
|
55
63
|
# GET /api/v1/connections/applications/{app_id}
|
56
|
-
def get(self, app_id: str) -> GetConnectionResponse:
|
64
|
+
def get(self, app_id: str) -> GetConnectionResponse | None:
|
57
65
|
try:
|
58
|
-
|
66
|
+
path = (
|
67
|
+
f"/connections/applications/{app_id}"
|
68
|
+
if is_cpd_env(self.base_url)
|
69
|
+
else f"/connections/applications?app_id={app_id}"
|
70
|
+
)
|
71
|
+
return GetConnectionResponse.model_validate(self._get(path))
|
59
72
|
except ClientAPIException as e:
|
60
73
|
if e.response.status_code == 404:
|
61
74
|
return None
|
@@ -65,7 +78,14 @@ class ConnectionsClient(BaseAPIClient):
|
|
65
78
|
# GET api/v1/connections/applications
|
66
79
|
def list(self) -> List[ListConfigsResponse]:
|
67
80
|
try:
|
68
|
-
|
81
|
+
path = (
|
82
|
+
f"/connections/applications"
|
83
|
+
if is_cpd_env(self.base_url)
|
84
|
+
else f"/connections/applications?include_details=true"
|
85
|
+
)
|
86
|
+
res = self._get(path)
|
87
|
+
import json
|
88
|
+
json.dumps(res)
|
69
89
|
return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
|
70
90
|
except ValidationError as e:
|
71
91
|
logger.error("Recieved unexpected response from server")
|
@@ -96,28 +116,38 @@ class ConnectionsClient(BaseAPIClient):
|
|
96
116
|
|
97
117
|
# POST /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
98
118
|
# POST /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
99
|
-
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict,
|
100
|
-
if
|
119
|
+
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
|
120
|
+
if use_app_credentials:
|
101
121
|
self._post(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
102
122
|
else:
|
103
123
|
self._post(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
104
124
|
|
105
125
|
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
106
126
|
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
107
|
-
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict,
|
108
|
-
if
|
127
|
+
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
|
128
|
+
if use_app_credentials:
|
109
129
|
self._patch(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
110
130
|
else:
|
111
131
|
self._patch(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
112
132
|
|
113
133
|
# GET /api/v1/connections/applications/{app_id}/configs/credentials?env={env}
|
114
134
|
# GET /api/v1/connections/applications/{app_id}/configs/runtime_credentials?env={env}
|
115
|
-
def get_credentials(self, app_id: str, env: ConnectionEnvironment,
|
135
|
+
def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> dict:
|
116
136
|
try:
|
117
|
-
if
|
118
|
-
|
137
|
+
if use_app_credentials:
|
138
|
+
path = (
|
139
|
+
f"/connections/applications/{app_id}/credentials?env={env}"
|
140
|
+
if is_cpd_env(self.base_url)
|
141
|
+
else f"/connections/applications/{app_id}/credentials/{env}"
|
142
|
+
)
|
143
|
+
return self._get(path)
|
119
144
|
else:
|
120
|
-
|
145
|
+
path = (
|
146
|
+
f"/connections/applications/{app_id}/configs/runtime_credentials?env={env}"
|
147
|
+
if is_cpd_env(self.base_url)
|
148
|
+
else f"/connections/applications/runtime_credentials?app_id={app_id}&env={env}"
|
149
|
+
)
|
150
|
+
return self._get(path)
|
121
151
|
except ClientAPIException as e:
|
122
152
|
if e.response.status_code == 404:
|
123
153
|
return None
|
@@ -125,8 +155,8 @@ class ConnectionsClient(BaseAPIClient):
|
|
125
155
|
|
126
156
|
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
127
157
|
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
128
|
-
def delete_credentials(self, app_id: str, env: ConnectionEnvironment,
|
129
|
-
if
|
158
|
+
def delete_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> None:
|
159
|
+
if use_app_credentials:
|
130
160
|
self._delete(f"/connections/applications/{app_id}/configs/{env}/credentials")
|
131
161
|
else:
|
132
162
|
self._delete(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials")
|
@@ -147,7 +177,12 @@ class ConnectionsClient(BaseAPIClient):
|
|
147
177
|
if conn_id is None:
|
148
178
|
return ""
|
149
179
|
try:
|
150
|
-
|
180
|
+
path = (
|
181
|
+
f"/connections/applications/id/{conn_id}"
|
182
|
+
if is_cpd_env(self.base_url)
|
183
|
+
else f"/connections/applications?connection_id={conn_id}"
|
184
|
+
)
|
185
|
+
app_details = self._get(path)
|
151
186
|
return app_details.get("app_id")
|
152
187
|
except ClientAPIException as e:
|
153
188
|
if e.response.status_code == 404:
|
@@ -17,14 +17,16 @@ 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"
|
21
20
|
return url
|
22
21
|
return None
|
23
22
|
|
24
23
|
def get_connections_client() -> ConnectionsClient:
|
25
24
|
return instantiate_client(client=ConnectionsClient, url=_get_connections_manager_url())
|
26
25
|
|
27
|
-
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType:
|
26
|
+
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType | None:
|
27
|
+
if security_scheme is None and auth_type is None:
|
28
|
+
return None
|
29
|
+
|
28
30
|
if security_scheme != ConnectionSecurityScheme.OAUTH2:
|
29
31
|
return ConnectionType(security_scheme)
|
30
32
|
return ConnectionType(auth_type)
|
@@ -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
|
@@ -14,7 +14,7 @@ DEFAULT_TENANT = {
|
|
14
14
|
DEFAULT_USER = {"username": "wxo.archer@ibm.com", "password": "watsonx"}
|
15
15
|
DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
|
16
16
|
DEFAULT_LOCAL_AUTH_ENDPOINT = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/auth/token"
|
17
|
-
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/tenants"
|
17
|
+
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/tenants"
|
18
18
|
DEFAULT_LOCAL_TENANT_AUTH_ENDPOINT = "{}/api/v1/auth/token?tenant_id={}"
|
19
19
|
|
20
20
|
|
@@ -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 'mcsp' 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 'mcsp' 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 'mcsp' 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
|
|
@@ -29,12 +29,17 @@ class TempusClient(BaseAPIClient):
|
|
29
29
|
authenticator=authenticator
|
30
30
|
)
|
31
31
|
|
32
|
+
def get_tempus_endpoint(self) -> str:
|
33
|
+
"""
|
34
|
+
Returns the Tempus endpoint URL
|
35
|
+
"""
|
36
|
+
return self.base_url
|
32
37
|
def create_update_flow_model(self, flow_id: str, model: dict) -> dict:
|
33
|
-
return self._post(f"/
|
38
|
+
return self._post(f"/flow-models/{flow_id}", data=model)
|
34
39
|
|
35
40
|
def run_flow(self, flow_id: str, input: dict) -> dict:
|
36
|
-
return self._post(f"/
|
41
|
+
return self._post(f"/flows/{flow_id}/versions/TIP/run", data=input)
|
37
42
|
|
38
43
|
def arun_flow(self, flow_id: str, input: dict) -> dict:
|
39
|
-
return self._post(f"/
|
44
|
+
return self._post(f"/flows/{flow_id}/versions/TIP/run/async", data=input)
|
40
45
|
|
@@ -9,7 +9,9 @@ from ibm_watsonx_orchestrate.cli.config import (
|
|
9
9
|
CONTEXT_SECTION_HEADER,
|
10
10
|
CONTEXT_ACTIVE_ENV_OPT,
|
11
11
|
ENVIRONMENTS_SECTION_HEADER,
|
12
|
-
ENV_WXO_URL_OPT
|
12
|
+
ENV_WXO_URL_OPT,
|
13
|
+
BYPASS_SSL,
|
14
|
+
VERIFY
|
13
15
|
)
|
14
16
|
from threading import Lock
|
15
17
|
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
|
@@ -45,6 +47,21 @@ def is_local_dev(url: str | None = None) -> bool:
|
|
45
47
|
|
46
48
|
return False
|
47
49
|
|
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)
|
54
|
+
|
55
|
+
if url.__contains__("cloud.ibm.com"):
|
56
|
+
return True
|
57
|
+
return False
|
58
|
+
|
59
|
+
|
60
|
+
def is_cpd_env(url: str) -> bool:
|
61
|
+
if url.lower().startswith("https://cpd"):
|
62
|
+
return True
|
63
|
+
return False
|
64
|
+
|
48
65
|
def check_token_validity(token: str) -> bool:
|
49
66
|
try:
|
50
67
|
token_claimset = jwt.decode(token, options={"verify_signature": False})
|
@@ -65,6 +82,17 @@ def instantiate_client(client: type[T] , url: str | None=None) -> T:
|
|
65
82
|
with open(os.path.join(DEFAULT_CONFIG_FILE_FOLDER, DEFAULT_CONFIG_FILE), "r") as f:
|
66
83
|
config = yaml_safe_load(f)
|
67
84
|
active_env = config.get(CONTEXT_SECTION_HEADER, {}).get(CONTEXT_ACTIVE_ENV_OPT)
|
85
|
+
bypass_ssl = (
|
86
|
+
config.get(ENVIRONMENTS_SECTION_HEADER, {})
|
87
|
+
.get(active_env, {})
|
88
|
+
.get(BYPASS_SSL, None)
|
89
|
+
)
|
90
|
+
|
91
|
+
verify = (
|
92
|
+
config.get(ENVIRONMENTS_SECTION_HEADER, {})
|
93
|
+
.get(active_env, {})
|
94
|
+
.get(VERIFY, None)
|
95
|
+
)
|
68
96
|
|
69
97
|
if not url:
|
70
98
|
url = config.get(ENVIRONMENTS_SECTION_HEADER, {}).get(active_env, {}).get(ENV_WXO_URL_OPT)
|
@@ -86,7 +114,14 @@ def instantiate_client(client: type[T] , url: str | None=None) -> T:
|
|
86
114
|
if not check_token_validity(token):
|
87
115
|
logger.error(f"The token found for environment '{active_env}' is missing or expired. Use `orchestrate env activate {active_env}` to fetch a new one")
|
88
116
|
exit(1)
|
89
|
-
|
117
|
+
is_cpd = is_cpd_env(url)
|
118
|
+
if is_cpd:
|
119
|
+
if bypass_ssl is True:
|
120
|
+
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
|
121
|
+
elif verify is not None:
|
122
|
+
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url), verify=verify)
|
123
|
+
else:
|
124
|
+
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url))
|
90
125
|
|
91
126
|
return client_instance
|
92
127
|
except FileNotFoundError as e:
|