ibm-watsonx-orchestrate 1.0.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 +28 -0
- ibm_watsonx_orchestrate/agent_builder/__init__.py +0 -0
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +5 -0
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +204 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +123 -0
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +260 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +59 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +243 -0
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +4 -0
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +36 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +332 -0
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +195 -0
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +162 -0
- ibm_watsonx_orchestrate/agent_builder/utils/__init__.py +0 -0
- ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +149 -0
- ibm_watsonx_orchestrate/cli/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +192 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +660 -0
- ibm_watsonx_orchestrate/cli/commands/channels/channels_command.py +15 -0
- ibm_watsonx_orchestrate/cli/commands/channels/channels_controller.py +16 -0
- ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -0
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_command.py +32 -0
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +141 -0
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +43 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +307 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +517 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +78 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +189 -0
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +9 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +79 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +201 -0
- ibm_watsonx_orchestrate/cli/commands/login/login_command.py +17 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +128 -0
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +623 -0
- ibm_watsonx_orchestrate/cli/commands/settings/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/langfuse_command.py +175 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/observability_command.py +11 -0
- ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py +10 -0
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +85 -0
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +564 -0
- ibm_watsonx_orchestrate/cli/commands/tools/types.py +10 -0
- ibm_watsonx_orchestrate/cli/config.py +226 -0
- ibm_watsonx_orchestrate/cli/main.py +32 -0
- ibm_watsonx_orchestrate/client/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +46 -0
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +38 -0
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +38 -0
- ibm_watsonx_orchestrate/client/analytics/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/analytics/llm/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +50 -0
- ibm_watsonx_orchestrate/client/base_api_client.py +113 -0
- ibm_watsonx_orchestrate/client/base_service_instance.py +10 -0
- ibm_watsonx_orchestrate/client/client.py +71 -0
- ibm_watsonx_orchestrate/client/client_errors.py +359 -0
- ibm_watsonx_orchestrate/client/connections/__init__.py +10 -0
- ibm_watsonx_orchestrate/client/connections/connections_client.py +162 -0
- ibm_watsonx_orchestrate/client/connections/utils.py +27 -0
- ibm_watsonx_orchestrate/client/credentials.py +123 -0
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +46 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +91 -0
- ibm_watsonx_orchestrate/client/service_instance.py +73 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +41 -0
- ibm_watsonx_orchestrate/client/utils.py +95 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +595 -0
- ibm_watsonx_orchestrate/docker/default.env +125 -0
- ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl +0 -0
- ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz +0 -0
- ibm_watsonx_orchestrate/docker/start-up.sh +61 -0
- ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -0
- ibm_watsonx_orchestrate/run/__init__.py +0 -0
- ibm_watsonx_orchestrate/run/connections.py +40 -0
- ibm_watsonx_orchestrate/utils/__init__.py +0 -0
- ibm_watsonx_orchestrate/utils/logging/__init__.py +0 -0
- ibm_watsonx_orchestrate/utils/logging/logger.py +26 -0
- ibm_watsonx_orchestrate/utils/logging/logging.yaml +18 -0
- ibm_watsonx_orchestrate/utils/utils.py +15 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/METADATA +34 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/RECORD +89 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/WHEEL +4 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/entry_points.txt +2 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,359 @@
|
|
1
|
+
# -----------------------------------------------------------------------------------------
|
2
|
+
# (C) Copyright IBM Corp. 2023-2024.
|
3
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
4
|
+
# -----------------------------------------------------------------------------------------
|
5
|
+
from __future__ import annotations
|
6
|
+
from typing import TYPE_CHECKING
|
7
|
+
|
8
|
+
import logging
|
9
|
+
import sys
|
10
|
+
import re
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from requests import Response
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"ClientError",
|
17
|
+
"MissingValue",
|
18
|
+
"MissingMetaProp",
|
19
|
+
"NotUrlNorID",
|
20
|
+
"NoCredentialsProvided",
|
21
|
+
"ApiRequestFailure",
|
22
|
+
"UnexpectedType",
|
23
|
+
"ForbiddenActionForPlan",
|
24
|
+
"NoVirtualDeploymentSupportedForICP",
|
25
|
+
"MissingArgument",
|
26
|
+
"WrongEnvironmentVersion",
|
27
|
+
"CannotAutogenerateBedrockUrl",
|
28
|
+
"WrongMetaProps",
|
29
|
+
"CannotSetProjectOrSpace",
|
30
|
+
"ForbiddenActionForGitBasedProject",
|
31
|
+
"CannotInstallLibrary",
|
32
|
+
"DataStreamError",
|
33
|
+
"WrongLocationProperty",
|
34
|
+
"WrongFileLocation",
|
35
|
+
"EmptyDataSource",
|
36
|
+
"SpaceIDandProjectIDCannotBeNone",
|
37
|
+
"ParamOutOfRange",
|
38
|
+
"InvalidMultipleArguments",
|
39
|
+
"ValidationError",
|
40
|
+
"InvalidValue",
|
41
|
+
"PromptVariablesError",
|
42
|
+
"UnsupportedOperation",
|
43
|
+
"MissingExtension",
|
44
|
+
"InvalidCredentialsError",
|
45
|
+
]
|
46
|
+
|
47
|
+
|
48
|
+
class ClientError(Exception):
|
49
|
+
def __init__(
|
50
|
+
self,
|
51
|
+
error_msg: str,
|
52
|
+
reason: str | list | None = None,
|
53
|
+
logg_messages: bool = True,
|
54
|
+
):
|
55
|
+
# Check if URL contains `internal` or `private` in host part of URL and hide it
|
56
|
+
pattern = (
|
57
|
+
r"\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)"
|
58
|
+
r"(?:[\w.-]*(internal|private)[^\s]*))"
|
59
|
+
)
|
60
|
+
self.error_msg = re.sub(
|
61
|
+
pattern,
|
62
|
+
lambda m: f"[{m.group(2).capitalize()} URL]",
|
63
|
+
str(error_msg),
|
64
|
+
re.IGNORECASE,
|
65
|
+
)
|
66
|
+
self.reason = reason
|
67
|
+
if logg_messages:
|
68
|
+
logging.getLogger(__name__).warning(self.__str__())
|
69
|
+
logging.getLogger(__name__).debug(
|
70
|
+
str(self.error_msg)
|
71
|
+
+ (
|
72
|
+
"\nReason: " + str(self.reason)
|
73
|
+
if sys.exc_info()[0] is not None
|
74
|
+
else ""
|
75
|
+
)
|
76
|
+
)
|
77
|
+
|
78
|
+
def __str__(self) -> str:
|
79
|
+
return str(self.error_msg) + (
|
80
|
+
"\nReason: " + str(self.reason) if self.reason is not None else ""
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
class MissingValue(ClientError, ValueError):
|
85
|
+
def __init__(self, value_name: str, reason: str | None = None):
|
86
|
+
ClientError.__init__(self, 'No "' + value_name + '" provided.', reason)
|
87
|
+
|
88
|
+
|
89
|
+
class MissingMetaProp(MissingValue):
|
90
|
+
def __init__(self, name: str, reason: str | None = None):
|
91
|
+
ClientError.__init__(
|
92
|
+
self, "Missing meta_prop with name: '{}'.".format(name), reason
|
93
|
+
)
|
94
|
+
|
95
|
+
|
96
|
+
class NotUrlNorID(ClientError, ValueError):
|
97
|
+
def __init__(self, value_name: str, value: str, reason: str | None = None):
|
98
|
+
ClientError.__init__(
|
99
|
+
self,
|
100
|
+
"Invalid value of '{}' - it is not url nor id: '{}'".format(
|
101
|
+
value_name, value
|
102
|
+
),
|
103
|
+
reason,
|
104
|
+
)
|
105
|
+
|
106
|
+
|
107
|
+
class NoCredentialsProvided(MissingValue):
|
108
|
+
def __init__(self, reason: str | None = None):
|
109
|
+
MissingValue.__init__(self, "WML credentials", reason)
|
110
|
+
|
111
|
+
|
112
|
+
class ApiRequestFailure(ClientError):
|
113
|
+
def __init__(self, error_msg: str, response: Response, reason: str | None = None):
|
114
|
+
self.response = response
|
115
|
+
if str(response.status_code) == "404" and "DOCTYPE" in str(response.content):
|
116
|
+
raise MissingWMLComponent()
|
117
|
+
|
118
|
+
elif str(
|
119
|
+
response.status_code
|
120
|
+
) == "400" and "Invalid content. You cannot include any tags in the HTTP request." in str(
|
121
|
+
response.content
|
122
|
+
):
|
123
|
+
ClientError.__init__(
|
124
|
+
self,
|
125
|
+
f"Please check if any parameter that you provided include HTTP tag. "
|
126
|
+
f"If yes, please remove it and try again.",
|
127
|
+
reason=str(response.content),
|
128
|
+
)
|
129
|
+
|
130
|
+
else:
|
131
|
+
ClientError.__init__(
|
132
|
+
self,
|
133
|
+
"{} ({} {})\nStatus code: {}, body: {}".format(
|
134
|
+
error_msg,
|
135
|
+
response.request.method,
|
136
|
+
response.request.url,
|
137
|
+
response.status_code,
|
138
|
+
(
|
139
|
+
response.text
|
140
|
+
if response.apparent_encoding is not None
|
141
|
+
else "[binary content, "
|
142
|
+
+ str(len(response.content))
|
143
|
+
+ " bytes]"
|
144
|
+
),
|
145
|
+
),
|
146
|
+
reason,
|
147
|
+
)
|
148
|
+
|
149
|
+
|
150
|
+
class UnexpectedType(ClientError, ValueError):
|
151
|
+
def __init__(self, el_name: str, expected_type: type, actual_type: type):
|
152
|
+
ClientError.__init__(
|
153
|
+
self,
|
154
|
+
"Unexpected type of '{}', expected: {}, actual: '{}'.".format(
|
155
|
+
el_name,
|
156
|
+
(
|
157
|
+
"'{}'".format(expected_type)
|
158
|
+
if type(expected_type) == type
|
159
|
+
else expected_type
|
160
|
+
),
|
161
|
+
actual_type,
|
162
|
+
),
|
163
|
+
)
|
164
|
+
|
165
|
+
|
166
|
+
class ForbiddenActionForPlan(ClientError):
|
167
|
+
def __init__(self, operation_name: str, expected_plans: list, actual_plan: str):
|
168
|
+
ClientError.__init__(
|
169
|
+
self,
|
170
|
+
"Operation '{}' is available only for {} plan, while this instance has '{}' plan.".format(
|
171
|
+
operation_name,
|
172
|
+
(
|
173
|
+
(
|
174
|
+
"one of {} as".format(expected_plans)
|
175
|
+
if len(expected_plans) > 1
|
176
|
+
else "'{}'".format(expected_plans[0])
|
177
|
+
)
|
178
|
+
if type(expected_plans) is list
|
179
|
+
else "'{}'".format(expected_plans)
|
180
|
+
),
|
181
|
+
actual_plan,
|
182
|
+
),
|
183
|
+
)
|
184
|
+
|
185
|
+
|
186
|
+
class NoVirtualDeploymentSupportedForICP(MissingValue):
|
187
|
+
def __init__(self, reason: str | None = None):
|
188
|
+
MissingValue.__init__(self, "No Virtual deployment supported for ICP", reason)
|
189
|
+
|
190
|
+
|
191
|
+
class MissingArgument(ClientError, ValueError):
|
192
|
+
def __init__(self, value_name: str, reason: str | None = None):
|
193
|
+
ClientError.__init__(self, f"Argument: {value_name} missing.", reason)
|
194
|
+
|
195
|
+
|
196
|
+
class WrongEnvironmentVersion(ClientError, ValueError):
|
197
|
+
def __init__(
|
198
|
+
self, used_version: str, environment_name: str, supported_versions: tuple
|
199
|
+
):
|
200
|
+
ClientError.__init__(
|
201
|
+
self,
|
202
|
+
"Version used in credentials not supported in this environment",
|
203
|
+
reason=f"Version {used_version} isn't supported in "
|
204
|
+
f"{environment_name} environment, "
|
205
|
+
f"select from {supported_versions}",
|
206
|
+
)
|
207
|
+
|
208
|
+
|
209
|
+
class CannotAutogenerateBedrockUrl(ClientError, ValueError):
|
210
|
+
def __init__(self, e1: Exception, e2: Exception):
|
211
|
+
ClientError.__init__(
|
212
|
+
self,
|
213
|
+
"Attempt of generating `bedrock_url` automatically failed. "
|
214
|
+
"If iamintegration=True, please provide `bedrock_url` in credentials. "
|
215
|
+
"If iamintegration=False, please validate your credentials.",
|
216
|
+
reason=[e1, e2],
|
217
|
+
)
|
218
|
+
|
219
|
+
|
220
|
+
class WrongMetaProps(MissingValue):
|
221
|
+
def __init__(self, reason: str | None = None):
|
222
|
+
ClientError.__init__(self, "Wrong metaprops.", reason)
|
223
|
+
|
224
|
+
|
225
|
+
class MissingWMLComponent(ClientError):
|
226
|
+
def __init__(self) -> None:
|
227
|
+
ClientError.__init__(
|
228
|
+
self,
|
229
|
+
f"Missing WML Component",
|
230
|
+
reason=f"It appears that WML component is not installed on your environment. "
|
231
|
+
f"Contact your cluster administrator.",
|
232
|
+
)
|
233
|
+
|
234
|
+
|
235
|
+
class CannotSetProjectOrSpace(ClientError):
|
236
|
+
def __init__(self, reason: str):
|
237
|
+
ClientError.__init__(self, f"Cannot set Project or Space", reason=reason)
|
238
|
+
|
239
|
+
|
240
|
+
class ForbiddenActionForGitBasedProject(ClientError):
|
241
|
+
def __init__(self, reason: str):
|
242
|
+
ClientError.__init__(
|
243
|
+
self, f"This action is not supported for git based project.", reason=reason
|
244
|
+
)
|
245
|
+
|
246
|
+
|
247
|
+
class CannotInstallLibrary(ClientError, ValueError):
|
248
|
+
def __init__(self, lib_name: str, reason: str):
|
249
|
+
ClientError.__init__(
|
250
|
+
self,
|
251
|
+
f"Library '{lib_name}' cannot be installed! Please install it manually.",
|
252
|
+
reason,
|
253
|
+
)
|
254
|
+
|
255
|
+
|
256
|
+
class DataStreamError(ClientError, ConnectionError):
|
257
|
+
def __init__(self, reason: str):
|
258
|
+
ClientError.__init__(
|
259
|
+
self, "Cannot fetch data via Flight Service. Try again.", reason
|
260
|
+
)
|
261
|
+
|
262
|
+
|
263
|
+
class WrongLocationProperty(ClientError, ConnectionError):
|
264
|
+
def __init__(self, reason: str):
|
265
|
+
ClientError.__init__(
|
266
|
+
self, "Cannot fetch data via Flight Service. Try again.", reason
|
267
|
+
)
|
268
|
+
|
269
|
+
|
270
|
+
class WrongFileLocation(ClientError, ValueError):
|
271
|
+
def __init__(self, reason: str):
|
272
|
+
ClientError.__init__(
|
273
|
+
self, "Cannot fetch data via Flight Service. Try again.", reason
|
274
|
+
)
|
275
|
+
|
276
|
+
|
277
|
+
class EmptyDataSource(ClientError, ValueError):
|
278
|
+
def __init__(self) -> None:
|
279
|
+
ClientError.__init__(
|
280
|
+
self,
|
281
|
+
"Cannot fetch data via Flight Service. "
|
282
|
+
"Verify if data were saved under data connection and try again.",
|
283
|
+
)
|
284
|
+
|
285
|
+
|
286
|
+
class SpaceIDandProjectIDCannotBeNone(ClientError, ValueError):
|
287
|
+
def __init__(self, reason: str):
|
288
|
+
ClientError.__init__(self, f"Missing 'space_id' or 'project_id'.", reason)
|
289
|
+
|
290
|
+
|
291
|
+
class ParamOutOfRange(ClientError, ValueError):
|
292
|
+
def __init__(
|
293
|
+
self, param_name: str, value: int | float, min: int | float, max: int | float
|
294
|
+
):
|
295
|
+
ClientError.__init__(
|
296
|
+
self,
|
297
|
+
f"Value of parameter `{param_name}`, {value}, is out of expected range - between {min} and {max}.",
|
298
|
+
)
|
299
|
+
|
300
|
+
|
301
|
+
class InvalidMultipleArguments(ClientError, ValueError):
|
302
|
+
def __init__(self, params_names_list: list, reason: str | None = None):
|
303
|
+
ClientError.__init__(
|
304
|
+
self, f"One of {params_names_list} parameters should be set.", reason
|
305
|
+
)
|
306
|
+
|
307
|
+
|
308
|
+
class ValidationError(ClientError, KeyError):
|
309
|
+
def __init__(self, key: str, additional_msg: str | None = None):
|
310
|
+
msg = (
|
311
|
+
f"Invalid prompt template; check for"
|
312
|
+
f" mismatched or missing input variables."
|
313
|
+
f" Missing input variable: {key}."
|
314
|
+
)
|
315
|
+
if additional_msg is not None:
|
316
|
+
msg += "\n" + additional_msg
|
317
|
+
ClientError.__init__(self, msg)
|
318
|
+
|
319
|
+
|
320
|
+
class PromptVariablesError(ClientError, KeyError):
|
321
|
+
def __init__(self, key: str):
|
322
|
+
ClientError.__init__(
|
323
|
+
self,
|
324
|
+
(
|
325
|
+
f"Prompt template contains input variables."
|
326
|
+
f" Missing {key} in `prompt_variables`"
|
327
|
+
),
|
328
|
+
)
|
329
|
+
|
330
|
+
|
331
|
+
class InvalidValue(ClientError, ValueError):
|
332
|
+
def __init__(self, value_name: str, reason: str | None = None):
|
333
|
+
ClientError.__init__(
|
334
|
+
self, 'Inappropriate value of "' + value_name + '"', reason
|
335
|
+
)
|
336
|
+
|
337
|
+
|
338
|
+
class UnsupportedOperation(ClientError):
|
339
|
+
def __init__(self, reason: str):
|
340
|
+
ClientError.__init__(
|
341
|
+
self, f"Operation is unsupported for this release.", reason
|
342
|
+
)
|
343
|
+
|
344
|
+
|
345
|
+
class MissingExtension(ClientError, ImportError):
|
346
|
+
def __init__(self, extension_name: str):
|
347
|
+
ClientError.__init__(
|
348
|
+
self,
|
349
|
+
f"Could not import {extension_name}: Please install `{extension_name}` extension.",
|
350
|
+
)
|
351
|
+
|
352
|
+
|
353
|
+
class InvalidCredentialsError(ClientError):
|
354
|
+
def __init__(self, reason: str | Response, logg_messages: bool = True):
|
355
|
+
ClientError.__init__(
|
356
|
+
self,
|
357
|
+
f"Attempt of authenticating connection to service failed, please validate your credentials. Error: {reason}",
|
358
|
+
logg_messages=logg_messages,
|
359
|
+
)
|
@@ -0,0 +1,162 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from pydantic import BaseModel, ValidationError
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
7
|
+
from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
|
8
|
+
|
9
|
+
import logging
|
10
|
+
logger = logging.getLogger(__name__)
|
11
|
+
|
12
|
+
|
13
|
+
class ListConfigsResponse(BaseModel):
|
14
|
+
connection_id: str = None,
|
15
|
+
app_id: str = None
|
16
|
+
name: str = None
|
17
|
+
security_scheme: ConnectionSecurityScheme | None = None,
|
18
|
+
auth_type: ConnectionAuthType | None = None,
|
19
|
+
environment: ConnectionEnvironment | None = None,
|
20
|
+
preference: ConnectionPreference | None = None,
|
21
|
+
credentials_entered: bool | None = False
|
22
|
+
|
23
|
+
class GetConfigResponse(BaseModel):
|
24
|
+
config_id: str = None
|
25
|
+
tenant_id: str = None
|
26
|
+
app_id: str = None
|
27
|
+
environment: ConnectionEnvironment = None
|
28
|
+
preference: ConnectionPreference = None
|
29
|
+
auth_type: ConnectionAuthType | None = None
|
30
|
+
sso: bool = None
|
31
|
+
security_scheme: ConnectionSecurityScheme = None
|
32
|
+
server_url: str | None = None
|
33
|
+
idp_config_data: Optional[IdpConfigData] = None
|
34
|
+
app_config_data: Optional[AppConfigData] = None
|
35
|
+
|
36
|
+
class GetConnectionResponse(BaseModel):
|
37
|
+
connection_id: str = None
|
38
|
+
app_id: str = None
|
39
|
+
tenant_id: str = None
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
class ConnectionsClient(BaseAPIClient):
|
44
|
+
"""
|
45
|
+
Client to handle CRUD operations for Connections endpoint
|
46
|
+
"""
|
47
|
+
# POST api/v1/connections/applications
|
48
|
+
def create(self, payload: dict) -> None:
|
49
|
+
self._post("/connections/applications", data=payload)
|
50
|
+
|
51
|
+
# DELETE api/v1/connections/applications/{app_id}
|
52
|
+
def delete(self, app_id: str) -> dict:
|
53
|
+
return self._delete(f"/connections/applications/{app_id}")
|
54
|
+
|
55
|
+
# GET /api/v1/connections/applications/{app_id}
|
56
|
+
def get(self, app_id: str) -> GetConnectionResponse:
|
57
|
+
try:
|
58
|
+
return GetConnectionResponse.model_validate(self._get(f"/connections/applications/{app_id}"))
|
59
|
+
except ClientAPIException as e:
|
60
|
+
if e.response.status_code == 404:
|
61
|
+
return None
|
62
|
+
raise e
|
63
|
+
|
64
|
+
|
65
|
+
# GET api/v1/connections/applications
|
66
|
+
def list(self) -> List[ListConfigsResponse]:
|
67
|
+
try:
|
68
|
+
res = self._get(f"/connections/applications")
|
69
|
+
return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
|
70
|
+
except ValidationError as e:
|
71
|
+
logger.error("Recieved unexpected response from server")
|
72
|
+
raise e
|
73
|
+
except ClientAPIException as e:
|
74
|
+
if e.response.status_code == 404:
|
75
|
+
return []
|
76
|
+
raise e
|
77
|
+
|
78
|
+
|
79
|
+
# POST /api/v1/connections/applications/{app_id}/configurations
|
80
|
+
def create_config(self, app_id: str, payload: dict) -> None:
|
81
|
+
self._post(f"/connections/applications/{app_id}/configurations", data=payload)
|
82
|
+
|
83
|
+
# PATCH /api/v1/connections/applications/{app_id}/configurations/{env}
|
84
|
+
def update_config(self, app_id: str, env: ConnectionEnvironment, payload: dict) -> None:
|
85
|
+
self._patch(f"/connections/applications/{app_id}/configurations/{env}", data=payload)
|
86
|
+
|
87
|
+
# `GET /api/v1/connections/applications/{app_id}/configurations/{env}'
|
88
|
+
def get_config(self, app_id: str, env: ConnectionEnvironment) -> GetConfigResponse:
|
89
|
+
try:
|
90
|
+
res = self._get(f"/connections/applications/{app_id}/configurations/{env}")
|
91
|
+
return GetConfigResponse.model_validate(res)
|
92
|
+
except ClientAPIException as e:
|
93
|
+
if e.response.status_code == 404:
|
94
|
+
return None
|
95
|
+
raise e
|
96
|
+
|
97
|
+
# POST /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
98
|
+
# POST /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
99
|
+
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_sso: bool) -> None:
|
100
|
+
if use_sso:
|
101
|
+
self._post(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
102
|
+
else:
|
103
|
+
self._post(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
104
|
+
|
105
|
+
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
106
|
+
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
107
|
+
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_sso: bool) -> None:
|
108
|
+
if use_sso:
|
109
|
+
self._patch(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
110
|
+
else:
|
111
|
+
self._patch(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
112
|
+
|
113
|
+
# GET /api/v1/connections/applications/{app_id}/configs/credentials?env={env}
|
114
|
+
# GET /api/v1/connections/applications/{app_id}/configs/runtime_credentials?env={env}
|
115
|
+
def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_sso: bool) -> dict:
|
116
|
+
try:
|
117
|
+
if use_sso:
|
118
|
+
return self._get(f"/connections/applications/{app_id}/credentials?env={env}")
|
119
|
+
else:
|
120
|
+
return self._get(f"/connections/applications/{app_id}/configs/runtime_credentials?env={env}")
|
121
|
+
except ClientAPIException as e:
|
122
|
+
if e.response.status_code == 404:
|
123
|
+
return None
|
124
|
+
raise e
|
125
|
+
|
126
|
+
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
127
|
+
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
128
|
+
def delete_credentials(self, app_id: str, env: ConnectionEnvironment, use_sso: bool) -> None:
|
129
|
+
if use_sso:
|
130
|
+
self._delete(f"/connections/applications/{app_id}/configs/{env}/credentials")
|
131
|
+
else:
|
132
|
+
self._delete(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials")
|
133
|
+
|
134
|
+
def get_draft_by_app_id(self, app_id: str) -> GetConnectionResponse:
|
135
|
+
return self.get(app_id=app_id)
|
136
|
+
|
137
|
+
def get_draft_by_app_ids(self, app_ids: List[str]) -> List[GetConnectionResponse]:
|
138
|
+
connections = []
|
139
|
+
for app_id in app_ids:
|
140
|
+
connection = self.get_draft_by_app_id(app_id)
|
141
|
+
if connection:
|
142
|
+
connections += connection
|
143
|
+
return connections
|
144
|
+
|
145
|
+
def get_draft_by_id(self, conn_id) -> str:
|
146
|
+
"""Retrieve the app ID for a given connection ID."""
|
147
|
+
if conn_id is None:
|
148
|
+
return ""
|
149
|
+
try:
|
150
|
+
connections = self.list()
|
151
|
+
except ClientAPIException as e:
|
152
|
+
if e.response.status_code == 404:
|
153
|
+
logger.warning(f"Connections not found. Returning connection ID: {conn_id}")
|
154
|
+
return conn_id
|
155
|
+
raise
|
156
|
+
|
157
|
+
app_id = next((conn.app_id for conn in connections if conn.connection_id == conn_id), None)
|
158
|
+
|
159
|
+
if app_id is None:
|
160
|
+
logger.warning(f"Connection with ID {conn_id} not found. Returning connection ID.")
|
161
|
+
return conn_id
|
162
|
+
return app_id
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
|
2
|
+
from ibm_watsonx_orchestrate.client.connections.connections_client import ConnectionsClient
|
3
|
+
from ibm_watsonx_orchestrate.cli.config import Config, ENVIRONMENTS_SECTION_HEADER, CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, ENV_WXO_URL_OPT
|
4
|
+
from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionType, ConnectionAuthType, ConnectionSecurityScheme
|
5
|
+
|
6
|
+
LOCAL_CONNECTION_MANAGER_PORT = 3001
|
7
|
+
|
8
|
+
def _get_connections_manager_url() -> str:
|
9
|
+
cfg = Config()
|
10
|
+
active_env = cfg.get(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
11
|
+
url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
|
12
|
+
|
13
|
+
if is_local_dev(url):
|
14
|
+
url_parts = url.split(":")
|
15
|
+
url_parts[-1] = str(LOCAL_CONNECTION_MANAGER_PORT)
|
16
|
+
url = ":".join(url_parts)
|
17
|
+
url = url + "/api/v1"
|
18
|
+
return url
|
19
|
+
return None
|
20
|
+
|
21
|
+
def get_connections_client() -> ConnectionsClient:
|
22
|
+
return instantiate_client(client=ConnectionsClient, url=_get_connections_manager_url())
|
23
|
+
|
24
|
+
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType:
|
25
|
+
if security_scheme != ConnectionSecurityScheme.OAUTH2:
|
26
|
+
return ConnectionType(security_scheme)
|
27
|
+
return ConnectionType(auth_type)
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# -----------------------------------------------------------------------------------------
|
2
|
+
# (C) Copyright IBM Corp. 2024.
|
3
|
+
# https://opensource.org/licenses/BSD-3-Clause
|
4
|
+
# -----------------------------------------------------------------------------------------
|
5
|
+
|
6
|
+
from __future__ import annotations
|
7
|
+
|
8
|
+
import os
|
9
|
+
from typing import Any
|
10
|
+
|
11
|
+
|
12
|
+
class Credentials:
|
13
|
+
"""This class encapsulate passed credentials and additional params.
|
14
|
+
|
15
|
+
:param url: URL of the service
|
16
|
+
:type url: str
|
17
|
+
|
18
|
+
:param api_key: service API key used in API key authentication
|
19
|
+
:type api_key: str, optional
|
20
|
+
|
21
|
+
:param token: service token, used in token authentication
|
22
|
+
:type token: str, optional
|
23
|
+
|
24
|
+
:param verify: certificate verification flag
|
25
|
+
:type verify: bool, optional
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(
|
29
|
+
self,
|
30
|
+
*,
|
31
|
+
url: str | None = None,
|
32
|
+
iam_url: str | None = None,
|
33
|
+
api_key: str | None = None,
|
34
|
+
token: str | None = None,
|
35
|
+
verify: str | bool | None = None,
|
36
|
+
auth_type: str | None = None,
|
37
|
+
) -> None:
|
38
|
+
env_credentials = Credentials._get_values_from_env_vars()
|
39
|
+
self.url = url
|
40
|
+
self.iam_url = iam_url if iam_url is not None else "https://iam.platform.saas.ibm.com"
|
41
|
+
self.api_key = api_key
|
42
|
+
self.token = token
|
43
|
+
self.local_global_token = None
|
44
|
+
self.verify = verify
|
45
|
+
self.auth_type = auth_type
|
46
|
+
self._is_env_token = token is None and "token" in env_credentials
|
47
|
+
|
48
|
+
for k, v in env_credentials.items():
|
49
|
+
if self.__dict__.get(k) is None:
|
50
|
+
self.__dict__[k] = v
|
51
|
+
|
52
|
+
@staticmethod
|
53
|
+
def from_dict(dict: dict) -> Credentials:
|
54
|
+
creds = Credentials()
|
55
|
+
for k, v in dict.items():
|
56
|
+
setattr(creds, k, v)
|
57
|
+
|
58
|
+
return creds
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def _get_values_from_env_vars() -> dict[str, Any]:
|
62
|
+
def get_value_from_file(filename: str) -> str:
|
63
|
+
with open(filename, "r") as f:
|
64
|
+
return f.read()
|
65
|
+
|
66
|
+
def get_verify_value(x: str) -> bool | str:
|
67
|
+
if x in ["True", "False"]:
|
68
|
+
return x == "True"
|
69
|
+
else:
|
70
|
+
return x
|
71
|
+
|
72
|
+
env_vars_mapping = {
|
73
|
+
"WXO_CLIENT_VERIFY_REQUESTS": lambda x: ("verify", get_verify_value(x)),
|
74
|
+
"USER_ACCESS_TOKEN": lambda x: ("token", x.replace("Bearer ", "")),
|
75
|
+
"RUNTIME_ENV_ACCESS_TOKEN_FILE": lambda x: (
|
76
|
+
"token",
|
77
|
+
get_value_from_file(x).replace("Bearer ", ""),
|
78
|
+
),
|
79
|
+
"WXO_URL": lambda x: ("url", x),
|
80
|
+
}
|
81
|
+
|
82
|
+
return dict(
|
83
|
+
[
|
84
|
+
f(os.environ[k])
|
85
|
+
for k, f in env_vars_mapping.items()
|
86
|
+
if os.environ.get(k) is not None and os.environ.get(k) != ""
|
87
|
+
]
|
88
|
+
)
|
89
|
+
|
90
|
+
def to_dict(self) -> dict[str, Any]:
|
91
|
+
"""Get dictionary from the Credentials object.
|
92
|
+
|
93
|
+
:return: dictionary with credentials
|
94
|
+
:rtype: dict
|
95
|
+
|
96
|
+
**Example**
|
97
|
+
|
98
|
+
.. code-block:: python
|
99
|
+
|
100
|
+
from ibm_watsonx_orchestrate import Credentials
|
101
|
+
|
102
|
+
credentials = Credentials.from_dict({
|
103
|
+
'url': "<url>",
|
104
|
+
'apikey': "<api_key>"
|
105
|
+
})
|
106
|
+
|
107
|
+
credentials_dict = credentials.to_dict()
|
108
|
+
|
109
|
+
"""
|
110
|
+
data = dict(
|
111
|
+
[
|
112
|
+
(k, v)
|
113
|
+
for k, v in self.__dict__.items()
|
114
|
+
if v is not None and not k.startswith("_")
|
115
|
+
]
|
116
|
+
)
|
117
|
+
return data
|
118
|
+
|
119
|
+
def __getitem__(self, key: str) -> Any:
|
120
|
+
return self.to_dict()[key]
|
121
|
+
|
122
|
+
def get(self, key: str, default: Any | None = None) -> Any:
|
123
|
+
return self.to_dict().get(key, default)
|