ibm-watsonx-orchestrate 1.3.0__py3-none-any.whl → 1.4.2__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/types.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +9 -2
- ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +32 -0
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +42 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +10 -1
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +4 -2
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +29 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +271 -12
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +17 -2
- ibm_watsonx_orchestrate/cli/commands/models/env_file_model_provider_mapper.py +180 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +194 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +117 -48
- ibm_watsonx_orchestrate/cli/commands/server/types.py +105 -0
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +55 -7
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +123 -42
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +22 -1
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +197 -12
- ibm_watsonx_orchestrate/client/agents/agent_client.py +4 -1
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +5 -1
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +5 -1
- ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +2 -6
- ibm_watsonx_orchestrate/client/base_api_client.py +5 -2
- ibm_watsonx_orchestrate/client/connections/connections_client.py +3 -9
- ibm_watsonx_orchestrate/client/model_policies/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +47 -0
- ibm_watsonx_orchestrate/client/model_policies/types.py +36 -0
- ibm_watsonx_orchestrate/client/models/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/models/models_client.py +46 -0
- ibm_watsonx_orchestrate/client/models/types.py +177 -0
- ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py +15 -6
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +40 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +8 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +68 -13
- ibm_watsonx_orchestrate/docker/default.env +22 -12
- ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -1
- ibm_watsonx_orchestrate/experimental/flow_builder/__init__.py +0 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/__init__.py +41 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/constants.py +17 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/data_map.py +91 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +143 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/events.py +72 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/flow.py +1288 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/node.py +97 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/resources/flow_status.openapi.yml +98 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/types.py +492 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +113 -0
- ibm_watsonx_orchestrate/utils/utils.py +5 -2
- {ibm_watsonx_orchestrate-1.3.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/METADATA +4 -1
- {ibm_watsonx_orchestrate-1.3.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/RECORD +54 -32
- {ibm_watsonx_orchestrate-1.3.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.3.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.3.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -7,18 +7,24 @@ import sys
|
|
7
7
|
import tempfile
|
8
8
|
import time
|
9
9
|
from pathlib import Path
|
10
|
+
from urllib.parse import urlparse
|
10
11
|
|
11
12
|
import jwt
|
12
13
|
import requests
|
13
14
|
import typer
|
14
15
|
from dotenv import dotenv_values
|
15
16
|
|
16
|
-
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
17
|
+
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
18
|
+
|
19
|
+
from ibm_watsonx_orchestrate.cli.commands.server.types import WatsonXAIEnvConfig, ModelGatewayEnvConfig
|
20
|
+
|
17
21
|
from ibm_watsonx_orchestrate.cli.commands.environment.environment_controller import _login
|
22
|
+
|
18
23
|
from ibm_watsonx_orchestrate.cli.config import LICENSE_HEADER, \
|
19
24
|
ENV_ACCEPT_LICENSE
|
20
25
|
from ibm_watsonx_orchestrate.cli.config import PROTECTED_ENV_NAME, clear_protected_env_credentials_token, Config, \
|
21
|
-
AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, USER_ENV_CACHE_HEADER
|
26
|
+
AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, USER_ENV_CACHE_HEADER, LICENSE_HEADER, \
|
27
|
+
ENV_ACCEPT_LICENSE
|
22
28
|
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
|
23
29
|
|
24
30
|
logger = logging.getLogger(__name__)
|
@@ -60,7 +66,9 @@ def docker_login(api_key: str, registry_url: str, username:str = "iamapikey") ->
|
|
60
66
|
logger.info("Successfully logged in to Docker.")
|
61
67
|
|
62
68
|
def docker_login_by_dev_edition_source(env_dict: dict, source: str) -> None:
|
63
|
-
|
69
|
+
if not env_dict.get("REGISTRY_URL"):
|
70
|
+
raise ValueError("REGISTRY_URL is not set.")
|
71
|
+
registry_url = env_dict["REGISTRY_URL"].split("/")[0]
|
64
72
|
if source == "internal":
|
65
73
|
iam_api_key = env_dict.get("DOCKER_IAM_KEY")
|
66
74
|
if not iam_api_key:
|
@@ -73,8 +81,6 @@ def docker_login_by_dev_edition_source(env_dict: dict, source: str) -> None:
|
|
73
81
|
docker_login(wo_entitlement_key, registry_url, "cp")
|
74
82
|
elif source == "orchestrate":
|
75
83
|
wo_auth_type = env_dict.get("WO_AUTH_TYPE")
|
76
|
-
if not wo_auth_type:
|
77
|
-
raise ValueError("WO_AUTH_TYPE is required in the environment file if WO_DEVELOPER_EDITION_SOURCE is set to 'orchestrate'.")
|
78
84
|
api_key, username = get_docker_cred_by_wo_auth_type(env_dict, wo_auth_type)
|
79
85
|
docker_login(api_key, registry_url, username)
|
80
86
|
|
@@ -108,24 +114,39 @@ def merge_env(
|
|
108
114
|
|
109
115
|
return merged
|
110
116
|
|
111
|
-
def get_default_registry_env_vars_by_dev_edition_source(
|
112
|
-
component_registry_var_names = {key for key in
|
113
|
-
|
114
|
-
|
115
|
-
if
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
117
|
+
def get_default_registry_env_vars_by_dev_edition_source(default_env: dict, user_env:dict, source: str) -> dict[str,str]:
|
118
|
+
component_registry_var_names = {key for key in default_env if key.endswith("_REGISTRY")} | {'REGISTRY_URL'}
|
119
|
+
|
120
|
+
registry_url = user_env.get("REGISTRY_URL", None)
|
121
|
+
if not registry_url:
|
122
|
+
if source == "internal":
|
123
|
+
registry_url = "us.icr.io/watson-orchestrate-private"
|
124
|
+
elif source == "myibm":
|
125
|
+
registry_url = "cp.icr.io/cp/wxo-lite"
|
126
|
+
elif source == "orchestrate":
|
127
|
+
# extract the hostname from the WO_INSTANCE URL, and replace the "api." prefix with "registry." to construct the registry URL per region
|
128
|
+
wo_url = user_env.get("WO_INSTANCE")
|
129
|
+
|
130
|
+
if not wo_url:
|
131
|
+
raise ValueError("WO_INSTANCE is required in the environment file if the developer edition source is set to 'orchestrate'.")
|
132
|
+
|
133
|
+
parsed = urlparse(wo_url)
|
134
|
+
hostname = parsed.hostname
|
135
|
+
|
136
|
+
if not hostname or not hostname.startswith("api."):
|
137
|
+
raise ValueError(f"Invalid WO_INSTANCE URL: '{wo_url}'. It should starts with 'api.'")
|
138
|
+
|
139
|
+
registry_url = f"registry.{hostname[4:]}/cp/wxo-lite"
|
140
|
+
else:
|
141
|
+
raise ValueError(f"Unknown value for developer edition source: {source}. Must be one of ['internal', 'myibm', 'orchestrate'].")
|
142
|
+
|
143
|
+
result = {name: registry_url for name in component_registry_var_names}
|
126
144
|
return result
|
127
145
|
|
128
|
-
def get_dev_edition_source(env_dict: dict) -> str:
|
146
|
+
def get_dev_edition_source(env_dict: dict | None) -> str:
|
147
|
+
if not env_dict:
|
148
|
+
return "myibm"
|
149
|
+
|
129
150
|
source = env_dict.get("WO_DEVELOPER_EDITION_SOURCE")
|
130
151
|
|
131
152
|
if source:
|
@@ -134,12 +155,28 @@ def get_dev_edition_source(env_dict: dict) -> str:
|
|
134
155
|
return "orchestrate"
|
135
156
|
return "myibm"
|
136
157
|
|
137
|
-
def get_docker_cred_by_wo_auth_type(env_dict: dict, auth_type: str) -> tuple[str, str]:
|
158
|
+
def get_docker_cred_by_wo_auth_type(env_dict: dict, auth_type: str | None) -> tuple[str, str]:
|
159
|
+
# Try infer the auth type if not provided
|
160
|
+
if not auth_type:
|
161
|
+
instance_url = env_dict.get("WO_INSTANCE")
|
162
|
+
if instance_url:
|
163
|
+
if ".cloud.ibm.com" in instance_url:
|
164
|
+
auth_type = "ibm_iam"
|
165
|
+
elif ".ibm.com" in instance_url:
|
166
|
+
auth_type = "mcsp"
|
167
|
+
|
138
168
|
if auth_type in {"mcsp", "ibm_iam"}:
|
139
169
|
wo_api_key = env_dict.get("WO_API_KEY")
|
140
170
|
if not wo_api_key:
|
141
171
|
raise ValueError("WO_API_KEY is required in the environment file if the WO_AUTH_TYPE is set to 'mcsp' or 'ibm_iam'.")
|
142
|
-
|
172
|
+
instance_url = env_dict.get("WO_INSTANCE")
|
173
|
+
if not instance_url:
|
174
|
+
raise ValueError("WO_INSTANCE is required in the environment file if the WO_AUTH_TYPE is set to 'mcsp' or 'ibm_iam'.")
|
175
|
+
path = urlparse(instance_url).path
|
176
|
+
if not path or '/' not in path:
|
177
|
+
raise ValueError(f"Invalid WO_INSTANCE URL: '{instance_url}'. It should contain the instance (tenant) id.")
|
178
|
+
tenant_id = path.split('/')[-1]
|
179
|
+
return wo_api_key, f"wxouser-{tenant_id}"
|
143
180
|
elif auth_type == "cpd":
|
144
181
|
wo_api_key = env_dict.get("WO_API_KEY")
|
145
182
|
wo_password = env_dict.get("WO_PASSWORD")
|
@@ -150,7 +187,36 @@ def get_docker_cred_by_wo_auth_type(env_dict: dict, auth_type: str) -> tuple[str
|
|
150
187
|
raise ValueError("WO_USERNAME is required in the environment file if the WO_AUTH_TYPE is set to 'cpd'.")
|
151
188
|
return wo_api_key or wo_password, wo_username # type: ignore[return-value]
|
152
189
|
else:
|
153
|
-
raise ValueError(f"Unknown value for WO_AUTH_TYPE: {auth_type}. Must be one of ['mcsp', 'ibm_iam', 'cpd'].")
|
190
|
+
raise ValueError(f"Unknown value for WO_AUTH_TYPE: '{auth_type}'. Must be one of ['mcsp', 'ibm_iam', 'cpd'].")
|
191
|
+
|
192
|
+
def apply_server_env_dict_defaults(provided_env_dict: dict) -> dict:
|
193
|
+
|
194
|
+
env_dict = provided_env_dict.copy()
|
195
|
+
|
196
|
+
env_dict['DBTAG'] = get_dbtag_from_architecture(merged_env_dict=env_dict)
|
197
|
+
|
198
|
+
model_config = None
|
199
|
+
try:
|
200
|
+
use_model_proxy = env_dict.get("USE_SAAS_ML_TOOLS_RUNTIME")
|
201
|
+
if not use_model_proxy or use_model_proxy.lower() != 'true':
|
202
|
+
model_config = WatsonXAIEnvConfig.model_validate(env_dict)
|
203
|
+
except ValueError:
|
204
|
+
pass
|
205
|
+
|
206
|
+
# If no watsonx ai detials are found, try build model gateway config
|
207
|
+
if not model_config:
|
208
|
+
try:
|
209
|
+
model_config = ModelGatewayEnvConfig.model_validate(env_dict)
|
210
|
+
except ValueError as e :
|
211
|
+
pass
|
212
|
+
|
213
|
+
if not model_config:
|
214
|
+
logger.error("Missing required model access environment variables. Please set Watson Orchestrate credentials 'WO_INSTANCE' and 'WO_API_KEY'. For CPD, set 'WO_INSTANCE', 'WO_USERNAME' and either 'WO_API_KEY' or 'WO_PASSWORD'. Alternatively, you can set WatsonX AI credentials directly using 'WATSONX_SPACE_ID' and 'WATSONX_APIKEY'")
|
215
|
+
sys.exit(1)
|
216
|
+
|
217
|
+
env_dict.update(model_config.model_dump(exclude_none=True))
|
218
|
+
|
219
|
+
return env_dict
|
154
220
|
|
155
221
|
def apply_llm_api_key_defaults(env_dict: dict) -> None:
|
156
222
|
llm_value = env_dict.get("WATSONX_APIKEY")
|
@@ -197,7 +263,7 @@ NON_SECRET_ENV_ITEMS = {
|
|
197
263
|
"WO_DEVELOPER_EDITION_SOURCE",
|
198
264
|
"WO_INSTANCE",
|
199
265
|
"USE_SAAS_ML_TOOLS_RUNTIME",
|
200
|
-
"
|
266
|
+
"AUTHORIZATION_URL",
|
201
267
|
"OPENSOURCE_REGISTRY_PROXY"
|
202
268
|
}
|
203
269
|
def persist_user_env(env: dict, include_secrets: bool = False) -> None:
|
@@ -218,7 +284,7 @@ def get_persisted_user_env() -> dict | None:
|
|
218
284
|
user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
|
219
285
|
return user_env
|
220
286
|
|
221
|
-
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False
|
287
|
+
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) -> None:
|
222
288
|
compose_path = get_compose_file()
|
223
289
|
compose_command = ensure_docker_compose_installed()
|
224
290
|
db_tag = read_env_file(final_env_file).get('DBTAG', None)
|
@@ -253,10 +319,6 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, wit
|
|
253
319
|
else:
|
254
320
|
command = compose_command
|
255
321
|
|
256
|
-
# Check if we start the server with tempus-runtime.
|
257
|
-
if with_flow_runtime:
|
258
|
-
command += ['--profile', 'with-tempus-runtime']
|
259
|
-
|
260
322
|
command += [
|
261
323
|
"-f", str(compose_path),
|
262
324
|
"--env-file", str(final_env_file),
|
@@ -338,14 +400,19 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
338
400
|
default_env = read_env_file(get_default_env_file())
|
339
401
|
user_env = read_env_file(user_env_file) if user_env_file else {}
|
340
402
|
if not user_env:
|
341
|
-
user_env = get_persisted_user_env()
|
403
|
+
user_env = get_persisted_user_env() or {}
|
342
404
|
|
343
405
|
dev_edition_source = get_dev_edition_source(user_env)
|
344
|
-
default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, source=dev_edition_source)
|
406
|
+
default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, user_env, source=dev_edition_source)
|
407
|
+
|
408
|
+
# Update the default environment with the default registry variables only if they are not already set
|
409
|
+
for key in default_registry_vars:
|
410
|
+
if key not in default_env or not default_env[key]:
|
411
|
+
default_env[key] = default_registry_vars[key]
|
345
412
|
|
413
|
+
# Merge the default environment with the user environment
|
346
414
|
merged_env_dict = {
|
347
415
|
**default_env,
|
348
|
-
**default_registry_vars,
|
349
416
|
**user_env,
|
350
417
|
}
|
351
418
|
|
@@ -498,6 +565,8 @@ def run_compose_lite_logs(final_env_file: Path, is_reset: bool = False) -> None:
|
|
498
565
|
command = compose_command + [
|
499
566
|
"-f", str(compose_path),
|
500
567
|
"--env-file", str(final_env_file),
|
568
|
+
"--profile",
|
569
|
+
"*",
|
501
570
|
"logs",
|
502
571
|
"-f"
|
503
572
|
]
|
@@ -557,15 +626,8 @@ def server_start(
|
|
557
626
|
experimental_with_langfuse: bool = typer.Option(
|
558
627
|
False,
|
559
628
|
'--with-langfuse', '-l',
|
560
|
-
help=''
|
629
|
+
help='Option to enable Langfuse support.'
|
561
630
|
),
|
562
|
-
with_flow_runtime: bool = typer.Option(
|
563
|
-
False,
|
564
|
-
'--with-tempus-runtime', '-f',
|
565
|
-
help='Option to start server with tempus-runtime.',
|
566
|
-
hidden=True
|
567
|
-
)
|
568
|
-
,
|
569
631
|
persist_env_secrets: bool = typer.Option(
|
570
632
|
False,
|
571
633
|
'--persist-env-secrets', '-p',
|
@@ -588,16 +650,26 @@ def server_start(
|
|
588
650
|
default_env = read_env_file(get_default_env_file())
|
589
651
|
user_env = read_env_file(user_env_file) if user_env_file else {}
|
590
652
|
persist_user_env(user_env, include_secrets=persist_env_secrets)
|
653
|
+
|
591
654
|
dev_edition_source = get_dev_edition_source(user_env)
|
592
|
-
default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, source=dev_edition_source)
|
655
|
+
default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, user_env, source=dev_edition_source)
|
593
656
|
|
657
|
+
# Update the default environment with the default registry variables only if they are not already set
|
658
|
+
for key in default_registry_vars:
|
659
|
+
if key not in default_env or not default_env[key]:
|
660
|
+
default_env[key] = default_registry_vars[key]
|
661
|
+
|
662
|
+
# Merge the default environment with the user environment
|
594
663
|
merged_env_dict = {
|
595
664
|
**default_env,
|
596
|
-
**default_registry_vars,
|
597
665
|
**user_env,
|
598
666
|
}
|
599
667
|
|
600
|
-
merged_env_dict
|
668
|
+
merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
|
669
|
+
|
670
|
+
# Add LANGFUSE_ENABLED into the merged_env_dict, for tempus to pick up.
|
671
|
+
if experimental_with_langfuse:
|
672
|
+
merged_env_dict['LANGFUSE_ENABLED'] = 'true'
|
601
673
|
|
602
674
|
try:
|
603
675
|
docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
|
@@ -609,7 +681,7 @@ def server_start(
|
|
609
681
|
|
610
682
|
|
611
683
|
final_env_file = write_merged_env_file(merged_env_dict)
|
612
|
-
run_compose_lite(final_env_file=final_env_file, experimental_with_langfuse=experimental_with_langfuse
|
684
|
+
run_compose_lite(final_env_file=final_env_file, experimental_with_langfuse=experimental_with_langfuse)
|
613
685
|
|
614
686
|
run_db_migration()
|
615
687
|
|
@@ -638,9 +710,6 @@ def server_start(
|
|
638
710
|
if experimental_with_langfuse:
|
639
711
|
logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
|
640
712
|
|
641
|
-
if with_flow_runtime:
|
642
|
-
logger.info(f"Starting with flow runtime")
|
643
|
-
|
644
713
|
@server_app.command(name="stop")
|
645
714
|
def server_stop(
|
646
715
|
user_env_file: str = typer.Option(
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import logging
|
2
|
+
import sys
|
3
|
+
from enum import Enum
|
4
|
+
from pydantic import BaseModel, model_validator, ConfigDict
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
class WoAuthType(str, Enum):
|
9
|
+
MCSP="mcsp"
|
10
|
+
IBM_IAM="ibm_iam"
|
11
|
+
CPD="cpd"
|
12
|
+
|
13
|
+
def __str__(self):
|
14
|
+
return self.value
|
15
|
+
|
16
|
+
def __repr__(self):
|
17
|
+
return repr(self.value)
|
18
|
+
|
19
|
+
AUTH_TYPE_DEFAULT_URL_MAPPING = {
|
20
|
+
WoAuthType.MCSP: "https://iam.platform.saas.ibm.com/siusermgr/api/1.0/apikeys/token",
|
21
|
+
WoAuthType.IBM_IAM: "https://iam.cloud.ibm.com/identity/token",
|
22
|
+
}
|
23
|
+
|
24
|
+
def _infer_auth_type_from_instance_url(instance_url: str) -> WoAuthType:
|
25
|
+
if ".cloud.ibm.com" in instance_url:
|
26
|
+
return WoAuthType.IBM_IAM
|
27
|
+
if ".ibm.com" in instance_url:
|
28
|
+
return WoAuthType.MCSP
|
29
|
+
return WoAuthType.CPD
|
30
|
+
|
31
|
+
|
32
|
+
class WatsonXAIEnvConfig(BaseModel):
|
33
|
+
WATSONX_SPACE_ID: str
|
34
|
+
WATSONX_APIKEY: str
|
35
|
+
USE_SAAS_ML_TOOLS_RUNTIME: bool
|
36
|
+
|
37
|
+
@model_validator(mode="before")
|
38
|
+
def validate_wxai_config(values):
|
39
|
+
relevant_fields = WatsonXAIEnvConfig.model_fields.keys()
|
40
|
+
config = {k: values.get(k) for k in relevant_fields}
|
41
|
+
|
42
|
+
if not config.get("WATSONX_SPACE_ID") and not config.get("WATSONX_APIKEY"):
|
43
|
+
raise ValueError("Missing configuration requirements 'WATSONX_SPACE_ID' and 'WATSONX_APIKEY'")
|
44
|
+
|
45
|
+
if config.get("WATSONX_SPACE_ID") and not config.get("WATSONX_APIKEY"):
|
46
|
+
logger.error("Cannot use env var 'WATSONX_SPACE_ID' without setting the corresponding 'WATSONX_APIKEY'")
|
47
|
+
sys.exit(1)
|
48
|
+
|
49
|
+
if not config.get("WATSONX_SPACE_ID") and config.get("WATSONX_APIKEY"):
|
50
|
+
logger.error("Cannot use env var 'WATSONX_APIKEY' without setting the corresponding 'WATSONX_SPACE_ID'")
|
51
|
+
sys.exit(1)
|
52
|
+
|
53
|
+
config["USE_SAAS_ML_TOOLS_RUNTIME"] = False
|
54
|
+
return config
|
55
|
+
|
56
|
+
|
57
|
+
class ModelGatewayEnvConfig(BaseModel):
|
58
|
+
WO_API_KEY: str | None = None
|
59
|
+
WO_USERNAME: str | None = None
|
60
|
+
WO_PASSWORD: str | None = None
|
61
|
+
WO_INSTANCE: str
|
62
|
+
AUTHORIZATION_URL: str
|
63
|
+
USE_SAAS_ML_TOOLS_RUNTIME: bool
|
64
|
+
WO_AUTH_TYPE: WoAuthType
|
65
|
+
WATSONX_SPACE_ID: str
|
66
|
+
|
67
|
+
@model_validator(mode="before")
|
68
|
+
def validate_model_gateway_config(values):
|
69
|
+
relevant_fields = ModelGatewayEnvConfig.model_fields.keys()
|
70
|
+
config = {k: values.get(k) for k in relevant_fields}
|
71
|
+
|
72
|
+
if not config.get("WO_INSTANCE"):
|
73
|
+
raise ValueError("Missing configuration requirements 'WO_INSTANCE'")
|
74
|
+
|
75
|
+
if not config.get("WO_AUTH_TYPE"):
|
76
|
+
inferred_auth_type = _infer_auth_type_from_instance_url(config.get("WO_INSTANCE"))
|
77
|
+
if not inferred_auth_type:
|
78
|
+
logger.error(f"Could not infer auth type from 'WO_INSTANCE'. Please set the 'WO_AUTH_TYPE' explictly")
|
79
|
+
sys.exit(1)
|
80
|
+
config["WO_AUTH_TYPE"] = inferred_auth_type
|
81
|
+
auth_type = config.get("WO_AUTH_TYPE")
|
82
|
+
|
83
|
+
if not config.get("AUTHORIZATION_URL"):
|
84
|
+
inferred_auth_url = AUTH_TYPE_DEFAULT_URL_MAPPING.get(auth_type)
|
85
|
+
if not inferred_auth_url:
|
86
|
+
logger.error(f"No 'AUTHORIZATION_URL' found. Auth type '{auth_type}' does not support defaulting. Please set the 'AUTHORIZATION_URL' explictly")
|
87
|
+
sys.exit(1)
|
88
|
+
config["AUTHORIZATION_URL"] = inferred_auth_url
|
89
|
+
|
90
|
+
if auth_type != WoAuthType.CPD:
|
91
|
+
if not config.get("WO_API_KEY"):
|
92
|
+
logger.error(f"Auth type '{auth_type}' requires 'WO_API_KEY' to be set as an env var.")
|
93
|
+
sys.exit(1)
|
94
|
+
else:
|
95
|
+
if not config.get("WO_USERNAME"):
|
96
|
+
logger.error("Auth type 'cpd' requires 'WO_USERNAME' to be set as an env var.")
|
97
|
+
sys.exit(1)
|
98
|
+
if not config.get("WO_API_KEY") and not config.get("WO_PASSWORD"):
|
99
|
+
logger.error("Auth type 'cpd' requires either 'WO_API_KEY' or 'WO_PASSWORD' to be set as env vars.")
|
100
|
+
sys.exit(1)
|
101
|
+
|
102
|
+
config["USE_SAAS_ML_TOOLS_RUNTIME"] = True
|
103
|
+
# Fake (but valid) UUIDv4 for knowledgebase check
|
104
|
+
config["WATSONX_SPACE_ID"] = "aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa"
|
105
|
+
return config
|
@@ -1,7 +1,12 @@
|
|
1
1
|
import typer
|
2
2
|
from typing import List
|
3
3
|
from typing_extensions import Annotated, Optional
|
4
|
-
from ibm_watsonx_orchestrate.
|
4
|
+
from ibm_watsonx_orchestrate.agent_builder.toolkits.types import ToolkitKind, Language
|
5
|
+
from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_controller import ToolkitController
|
6
|
+
import logging
|
7
|
+
import sys
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
5
10
|
|
6
11
|
toolkits_app = typer.Typer(no_args_is_help=True)
|
7
12
|
|
@@ -19,10 +24,18 @@ def import_toolkit(
|
|
19
24
|
str,
|
20
25
|
typer.Option("--description", help="Description of the toolkit"),
|
21
26
|
],
|
27
|
+
package: Annotated[
|
28
|
+
str,
|
29
|
+
typer.Option("--package", help="NPM or Python package of the MCP server"),
|
30
|
+
] = None,
|
22
31
|
package_root: Annotated[
|
23
32
|
str,
|
24
|
-
typer.Option("--package-root",
|
25
|
-
],
|
33
|
+
typer.Option("--package-root", help="Root directory of the MCP server package"),
|
34
|
+
] = None,
|
35
|
+
language: Annotated[
|
36
|
+
Language,
|
37
|
+
typer.Option("--language", "-l", help="Language your package is based on")
|
38
|
+
] = None,
|
26
39
|
command: Annotated[
|
27
40
|
str,
|
28
41
|
typer.Option(
|
@@ -31,7 +44,7 @@ def import_toolkit(
|
|
31
44
|
"or a JSON-style list of arguments (e.g. '[\"node\", \"dist/index.js\", \"--transport\", \"stdio\"]'). "
|
32
45
|
"The first argument will be used as the executable, the rest as its arguments."
|
33
46
|
),
|
34
|
-
],
|
47
|
+
] = None,
|
35
48
|
tools: Annotated[
|
36
49
|
Optional[str],
|
37
50
|
typer.Option("--tools", "-t", help="Comma-separated list of tools to import. Or you can use `*` to use all tools"),
|
@@ -40,26 +53,61 @@ def import_toolkit(
|
|
40
53
|
List[str],
|
41
54
|
typer.Option(
|
42
55
|
"--app-id", "-a",
|
43
|
-
help='The app
|
56
|
+
help='The app ids of the connections to associate with this tool. A application connection represents the server authentication credentials needed to connect to this tool. Only type key_value is currently supported for MCP.'
|
44
57
|
)
|
45
58
|
] = None
|
46
59
|
):
|
47
|
-
if tools == "*":
|
48
|
-
tool_list = ["*"]
|
60
|
+
if tools == "*": # Wildcard to use all tools for MCP
|
61
|
+
tool_list = ["*"]
|
49
62
|
elif tools:
|
50
63
|
tool_list = [tool.strip() for tool in tools.split(",")]
|
51
64
|
else:
|
52
65
|
tool_list = None
|
53
66
|
|
67
|
+
if not package and not package_root:
|
68
|
+
logger.error("You must provide either '--package' or '--package-root'.")
|
69
|
+
sys.exit(1)
|
70
|
+
|
71
|
+
if package_root and not command:
|
72
|
+
logger.error("Error: '--command' flag must be provided when '--package-root' is specified.")
|
73
|
+
sys.exit(1)
|
74
|
+
|
75
|
+
if package_root and package:
|
76
|
+
logger.error("Please choose either '--package-root' or '--package' but not both.")
|
77
|
+
sys.exit(1)
|
78
|
+
|
79
|
+
if package and not package_root:
|
80
|
+
if not command:
|
81
|
+
if language == Language.NODE:
|
82
|
+
command = f"npx -y {package}"
|
83
|
+
elif language == Language.PYTHON:
|
84
|
+
command = f"python -m {package}"
|
85
|
+
else:
|
86
|
+
logger.error("Unable to infer start up command: '--language' flag must be either 'node' or 'python' when using the '--package' flag without '--command' flag.")
|
87
|
+
sys.exit(1)
|
88
|
+
|
89
|
+
|
54
90
|
toolkit_controller = ToolkitController(
|
55
91
|
kind=kind,
|
56
92
|
name=name,
|
57
93
|
description=description,
|
94
|
+
package=package,
|
58
95
|
package_root=package_root,
|
96
|
+
language=language,
|
59
97
|
command=command,
|
60
98
|
)
|
61
99
|
toolkit_controller.import_toolkit(tools=tool_list, app_id=app_id)
|
62
100
|
|
101
|
+
@toolkits_app.command(name="list")
|
102
|
+
def list_toolkits(
|
103
|
+
verbose: Annotated[
|
104
|
+
bool,
|
105
|
+
typer.Option("--verbose", "-v", help="List full details of all toolkits as json"),
|
106
|
+
] = False,
|
107
|
+
):
|
108
|
+
toolkit_controller = ToolkitController()
|
109
|
+
toolkit_controller.list_toolkits(verbose=verbose)
|
110
|
+
|
63
111
|
@toolkits_app.command(name="remove")
|
64
112
|
def remove_toolkit(
|
65
113
|
name: Annotated[
|