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