ibm-watsonx-orchestrate 1.6.0b0__py3-none-any.whl → 1.6.1__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/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -1
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +21 -7
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +39 -36
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +56 -18
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -21
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +123 -5
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -3
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +107 -22
- ibm_watsonx_orchestrate/client/agents/agent_client.py +74 -6
- ibm_watsonx_orchestrate/client/base_api_client.py +2 -1
- ibm_watsonx_orchestrate/client/connections/connections_client.py +18 -9
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/service_instance.py +3 -3
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +10 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +228 -67
- ibm_watsonx_orchestrate/docker/default.env +32 -13
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +15 -5
- ibm_watsonx_orchestrate/flow_builder/utils.py +78 -48
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.1.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.1.dist-info}/RECORD +42 -37
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.1.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.1.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -24,6 +24,7 @@ from rich.console import Console
|
|
24
24
|
from rich.panel import Panel
|
25
25
|
|
26
26
|
from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool, ToolSpec
|
27
|
+
from ibm_watsonx_orchestrate.agent_builder.tools.flow_tool import create_flow_json_tool
|
27
28
|
from ibm_watsonx_orchestrate.agent_builder.tools.openapi_tool import create_openapi_json_tools_from_uri,create_openapi_json_tools_from_content
|
28
29
|
from ibm_watsonx_orchestrate.cli.commands.models.models_controller import ModelHighlighter
|
29
30
|
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
@@ -38,10 +39,9 @@ from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
|
|
38
39
|
from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
|
39
40
|
from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
|
40
41
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
|
42
|
+
from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_support_tools
|
41
43
|
from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
|
42
44
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
43
|
-
from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
|
44
|
-
from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_model
|
45
45
|
|
46
46
|
from ibm_watsonx_orchestrate import __version__
|
47
47
|
|
@@ -57,6 +57,12 @@ class ToolKind(str, Enum):
|
|
57
57
|
flow = "flow"
|
58
58
|
# skill = "skill"
|
59
59
|
|
60
|
+
def _get_connection_environments() -> List[ConnectionEnvironment]:
|
61
|
+
if is_local_dev():
|
62
|
+
return [ConnectionEnvironment.DRAFT]
|
63
|
+
else:
|
64
|
+
return [env.value for env in ConnectionEnvironment]
|
65
|
+
|
60
66
|
def validate_app_ids(kind: ToolKind, **args) -> None:
|
61
67
|
app_ids = args.get("app_id")
|
62
68
|
if not app_ids:
|
@@ -71,7 +77,14 @@ def validate_app_ids(kind: ToolKind, **args) -> None:
|
|
71
77
|
connections_client = get_connections_client()
|
72
78
|
|
73
79
|
imported_connections_list = connections_client.list()
|
74
|
-
imported_connections = {
|
80
|
+
imported_connections = {}
|
81
|
+
for conn in imported_connections_list:
|
82
|
+
app_id = conn.app_id
|
83
|
+
conn_env = conn.environment
|
84
|
+
if app_id in imported_connections:
|
85
|
+
imported_connections[app_id][conn_env] = conn
|
86
|
+
else:
|
87
|
+
imported_connections[app_id] = {conn_env: conn}
|
75
88
|
|
76
89
|
for app_id in app_ids:
|
77
90
|
if kind == ToolKind.python:
|
@@ -89,9 +102,29 @@ def validate_app_ids(kind: ToolKind, **args) -> None:
|
|
89
102
|
if app_id not in imported_connections:
|
90
103
|
logger.warning(f"No connection found for provided app-id '{app_id}'. Please create the connection using `orchestrate connections add`")
|
91
104
|
else:
|
92
|
-
|
93
|
-
|
94
|
-
|
105
|
+
# Validate that the connection is not key_value when the tool in openapi
|
106
|
+
if kind != ToolKind.openapi:
|
107
|
+
continue
|
108
|
+
|
109
|
+
environments = _get_connection_environments()
|
110
|
+
|
111
|
+
imported_connection = imported_connections.get(app_id)
|
112
|
+
|
113
|
+
for conn_environment in environments:
|
114
|
+
conn = imported_connection.get(conn_environment)
|
115
|
+
|
116
|
+
if conn is None or conn.security_scheme is None:
|
117
|
+
message = f"Connection '{app_id}' is not configured in the '{conn_environment}' environment."
|
118
|
+
if conn_environment == ConnectionEnvironment.DRAFT:
|
119
|
+
logger.error(message)
|
120
|
+
sys.exit(1)
|
121
|
+
else:
|
122
|
+
logger.warning(message + " If you deploy this tool without setting the live configuration the tool will error during execution.")
|
123
|
+
continue
|
124
|
+
|
125
|
+
if conn.security_scheme == ConnectionSecurityScheme.KEY_VALUE:
|
126
|
+
logger.error(f"Key value application connections can not be bound to an openapi tool")
|
127
|
+
exit(1)
|
95
128
|
|
96
129
|
def validate_params(kind: ToolKind, **args) -> None:
|
97
130
|
if kind in {"openapi", "python"} and args["file"] is None:
|
@@ -157,7 +190,14 @@ def validate_python_connections(tool: BaseTool):
|
|
157
190
|
|
158
191
|
provided_connections = list(connections.keys()) if connections else []
|
159
192
|
imported_connections_list = connections_client.list()
|
160
|
-
imported_connections = {
|
193
|
+
imported_connections = {}
|
194
|
+
for conn in imported_connections_list:
|
195
|
+
conn_id = conn.connection_id
|
196
|
+
conn_env = conn.environment
|
197
|
+
if conn_id in imported_connections:
|
198
|
+
imported_connections[conn_id][conn_env] = conn
|
199
|
+
else:
|
200
|
+
imported_connections[conn_id] = {conn_env: conn}
|
161
201
|
|
162
202
|
validation_failed = False
|
163
203
|
|
@@ -186,15 +226,30 @@ def validate_python_connections(tool: BaseTool):
|
|
186
226
|
|
187
227
|
connection_id = connections.get(sanatized_expected_tool_app_id)
|
188
228
|
imported_connection = imported_connections.get(connection_id)
|
189
|
-
imported_connection_auth_type = get_connection_type(security_scheme=imported_connection.security_scheme, auth_type=imported_connection.auth_type)
|
190
229
|
|
191
230
|
if connection_id and not imported_connection:
|
192
231
|
logger.error(f"The expected connection id '{connection_id}' does not match any known connection. This is likely caused by the connection being deleted. Please rec-reate the connection and re-import the tool")
|
193
232
|
validation_failed = True
|
233
|
+
|
234
|
+
environments = _get_connection_environments()
|
235
|
+
|
236
|
+
for conn_environment in environments:
|
237
|
+
conn = imported_connection.get(conn_environment)
|
238
|
+
conn_identifier = conn.app_id if conn is not None else connection_id
|
239
|
+
if conn is None or conn.security_scheme is None:
|
240
|
+
message = f"Connection '{conn_identifier}' is not configured in the '{conn_environment}' environment."
|
241
|
+
if conn_environment == ConnectionEnvironment.DRAFT:
|
242
|
+
logger.error(message)
|
243
|
+
sys.exit(1)
|
244
|
+
else:
|
245
|
+
logger.warning(message + " If you deploy this tool without setting the live configuration the tool will error during execution.")
|
246
|
+
continue
|
194
247
|
|
195
|
-
|
196
|
-
|
197
|
-
|
248
|
+
imported_connection_auth_type = get_connection_type(security_scheme=conn.security_scheme, auth_type=conn.auth_type)
|
249
|
+
|
250
|
+
if conn and len(expected_tool_conn_types) and imported_connection_auth_type not in expected_tool_conn_types:
|
251
|
+
logger.error(f"The app-id '{conn.app_id}' is of type '{imported_connection_auth_type.value}' in the '{conn_environment}' environment. The tool '{tool.__tool_spec__.name}' accepts connections of the following types '{', '.join(expected_tool_conn_types)}'. Use `orchestrate connections list` to view current connections and use `orchestrate connections add` to create the relevent connection")
|
252
|
+
validation_failed = True
|
198
253
|
|
199
254
|
if validation_failed:
|
200
255
|
exit(1)
|
@@ -345,17 +400,12 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
345
400
|
|
346
401
|
[bold cyan]Additional information:[/bold cyan]
|
347
402
|
|
348
|
-
- The [bold green]
|
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]
|
403
|
+
- The [bold green]Get flow status[/bold green] tool is being imported to support flow tools. This tool can query the status of a flow tool instance. You can add it to your agent using the UI or including the following tool name in your agent definition: [green]i__get_flow_status_intrinsic_tool__[/green].
|
350
404
|
|
351
405
|
"""
|
352
406
|
|
353
407
|
console.print(Panel(message, title="[bold blue]Flow tool support information[/bold blue]", border_style="bright_blue"))
|
354
408
|
|
355
|
-
|
356
|
-
if not is_local_dev():
|
357
|
-
raise typer.BadParameter(f"Flow tools are only supported in local environment.")
|
358
|
-
|
359
409
|
model = None
|
360
410
|
|
361
411
|
# Load the Flow JSON model from the file
|
@@ -422,7 +472,16 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
422
472
|
except Exception as e:
|
423
473
|
raise typer.BadParameter(f"Failed to load model from file {file}: {e}")
|
424
474
|
|
425
|
-
|
475
|
+
tool = create_flow_json_tool(name=model["spec"]["name"],
|
476
|
+
description=model["spec"]["description"],
|
477
|
+
permission="read_only",
|
478
|
+
flow_model=model)
|
479
|
+
|
480
|
+
tools = import_flow_support_tools()
|
481
|
+
|
482
|
+
tools.append(tool)
|
483
|
+
|
484
|
+
return tools
|
426
485
|
|
427
486
|
|
428
487
|
async def import_openapi_tool(file: str, connection_id: str) -> List[BaseTool]:
|
@@ -503,7 +562,20 @@ class ToolsController:
|
|
503
562
|
|
504
563
|
def list_tools(self, verbose=False):
|
505
564
|
response = self.get_client().get()
|
506
|
-
tool_specs = [
|
565
|
+
tool_specs = []
|
566
|
+
parse_errors = []
|
567
|
+
|
568
|
+
for tool in response:
|
569
|
+
try:
|
570
|
+
tool_specs.append(ToolSpec.model_validate(tool))
|
571
|
+
except Exception as e:
|
572
|
+
name = tool.get('name', None)
|
573
|
+
parse_errors.append([
|
574
|
+
f"Tool '{name}' could not be parsed",
|
575
|
+
json.dumps(tool),
|
576
|
+
e
|
577
|
+
])
|
578
|
+
|
507
579
|
tools = [BaseTool(spec=spec) for spec in tool_specs]
|
508
580
|
|
509
581
|
if verbose:
|
@@ -514,9 +586,16 @@ class ToolsController:
|
|
514
586
|
rich.print(JSON(json.dumps(tools_list, indent=4)))
|
515
587
|
else:
|
516
588
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
517
|
-
|
518
|
-
|
519
|
-
|
589
|
+
column_args = {
|
590
|
+
"Name": {"overflow": "fold"},
|
591
|
+
"Description": {},
|
592
|
+
"Permission": {},
|
593
|
+
"Type": {},
|
594
|
+
"Toolkit": {},
|
595
|
+
"App ID": {"overflow": "fold"}
|
596
|
+
}
|
597
|
+
for column in column_args:
|
598
|
+
table.add_column(column,**column_args[column])
|
520
599
|
|
521
600
|
connections_client = get_connections_client()
|
522
601
|
connections = connections_client.list()
|
@@ -558,6 +637,8 @@ class ToolsController:
|
|
558
637
|
tool_type=ToolKind.openapi
|
559
638
|
elif tool_binding.mcp is not None:
|
560
639
|
tool_type=ToolKind.mcp
|
640
|
+
elif tool_binding.flow is not None:
|
641
|
+
tool_type=ToolKind.flow
|
561
642
|
else:
|
562
643
|
tool_type="Unknown"
|
563
644
|
|
@@ -583,6 +664,10 @@ class ToolsController:
|
|
583
664
|
|
584
665
|
rich.print(table)
|
585
666
|
|
667
|
+
for error in parse_errors:
|
668
|
+
for l in error:
|
669
|
+
logger.error(l)
|
670
|
+
|
586
671
|
def get_all_tools(self) -> dict:
|
587
672
|
return {entry["name"]: entry["id"] for entry in self.get_client().get()}
|
588
673
|
|
@@ -3,6 +3,74 @@ from typing_extensions import List, Optional
|
|
3
3
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
4
4
|
from pydantic import BaseModel
|
5
5
|
|
6
|
+
def transform_agents_from_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
|
7
|
+
if isinstance(agents,list):
|
8
|
+
new_agents = []
|
9
|
+
for agent in agents:
|
10
|
+
new_agents.append(_transform_agent_from_flat_agent_spec(agent))
|
11
|
+
agents = new_agents
|
12
|
+
else:
|
13
|
+
agents = _transform_agent_from_flat_agent_spec(agents)
|
14
|
+
|
15
|
+
return agents
|
16
|
+
|
17
|
+
|
18
|
+
def _transform_agent_from_flat_agent_spec(agent_spec: dict ) -> dict:
|
19
|
+
transformed = {"additional_properties": {}}
|
20
|
+
for key,value in agent_spec.items():
|
21
|
+
if key == "starter_prompts":
|
22
|
+
if value:
|
23
|
+
value.pop("is_default_prompts",None)
|
24
|
+
value["customize"] = value.pop("prompts", [])
|
25
|
+
|
26
|
+
transformed["additional_properties"] |= { key: value }
|
27
|
+
|
28
|
+
elif key == "welcome_content":
|
29
|
+
if value:
|
30
|
+
value.pop("is_default_message", None)
|
31
|
+
|
32
|
+
transformed["additional_properties"] |= { key: value }
|
33
|
+
|
34
|
+
else:
|
35
|
+
transformed |= { key: value }
|
36
|
+
|
37
|
+
return transformed
|
38
|
+
|
39
|
+
def transform_agents_to_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
|
40
|
+
if isinstance(agents,list):
|
41
|
+
new_agents = []
|
42
|
+
for agent in agents:
|
43
|
+
new_agents.append(_transform_agent_to_flat_agent_spec(agent))
|
44
|
+
agents = new_agents
|
45
|
+
else:
|
46
|
+
agents = _transform_agent_to_flat_agent_spec(agents)
|
47
|
+
|
48
|
+
return agents
|
49
|
+
|
50
|
+
def _transform_agent_to_flat_agent_spec(agent_spec: dict ) -> dict:
|
51
|
+
additional_properties = agent_spec.get("additional_properties", None)
|
52
|
+
if not additional_properties:
|
53
|
+
return agent_spec
|
54
|
+
|
55
|
+
transformed = agent_spec
|
56
|
+
for key,value in additional_properties.items():
|
57
|
+
if key == "starter_prompts":
|
58
|
+
if value:
|
59
|
+
value["is_default_prompts"] = False
|
60
|
+
value["prompts"] = value.pop("customize", [])
|
61
|
+
|
62
|
+
transformed[key] = value
|
63
|
+
|
64
|
+
elif key == "welcome_content":
|
65
|
+
if value:
|
66
|
+
value["is_default_message"] = False
|
67
|
+
|
68
|
+
transformed[key] = value
|
69
|
+
|
70
|
+
transformed.pop("additional_properties",None)
|
71
|
+
|
72
|
+
return transformed
|
73
|
+
|
6
74
|
class AgentUpsertResponse(BaseModel):
|
7
75
|
id: Optional[str] = None
|
8
76
|
warning: Optional[str] = None
|
@@ -17,14 +85,14 @@ class AgentClient(BaseAPIClient):
|
|
17
85
|
|
18
86
|
|
19
87
|
def create(self, payload: dict) -> AgentUpsertResponse:
|
20
|
-
response = self._post(self.base_endpoint, data=payload)
|
88
|
+
response = self._post(self.base_endpoint, data=transform_agents_from_flat_agent_spec(payload))
|
21
89
|
return AgentUpsertResponse.model_validate(response)
|
22
90
|
|
23
91
|
def get(self) -> dict:
|
24
|
-
return self._get(f"{self.base_endpoint}?include_hidden=true")
|
92
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?include_hidden=true"))
|
25
93
|
|
26
94
|
def update(self, agent_id: str, data: dict) -> AgentUpsertResponse:
|
27
|
-
response = self._patch(f"{self.base_endpoint}/{agent_id}", data=data)
|
95
|
+
response = self._patch(f"{self.base_endpoint}/{agent_id}", data=transform_agents_from_flat_agent_spec(data))
|
28
96
|
return AgentUpsertResponse.model_validate(response)
|
29
97
|
|
30
98
|
def delete(self, agent_id: str) -> dict:
|
@@ -35,14 +103,14 @@ class AgentClient(BaseAPIClient):
|
|
35
103
|
|
36
104
|
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
37
105
|
formatted_agent_names = [f"names={x}" for x in agent_names]
|
38
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true")
|
106
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true"))
|
39
107
|
|
40
108
|
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
41
109
|
if agent_id is None:
|
42
110
|
return ""
|
43
111
|
else:
|
44
112
|
try:
|
45
|
-
agent = self._get(f"{self.base_endpoint}/{agent_id}")
|
113
|
+
agent = transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}/{agent_id}"))
|
46
114
|
return agent
|
47
115
|
except ClientAPIException as e:
|
48
116
|
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
@@ -51,5 +119,5 @@ class AgentClient(BaseAPIClient):
|
|
51
119
|
|
52
120
|
def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
|
53
121
|
formatted_agent_ids = [f"ids={x}" for x in agent_ids]
|
54
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true")
|
122
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true"))
|
55
123
|
|
@@ -36,6 +36,8 @@ class BaseAPIClient:
|
|
36
36
|
|
37
37
|
if not self.is_local:
|
38
38
|
self.base_url = f"{self.base_url}/v1/orchestrate"
|
39
|
+
else:
|
40
|
+
self.base_url = f"{self.base_url}/v1"
|
39
41
|
|
40
42
|
def _get_headers(self) -> dict:
|
41
43
|
headers = {}
|
@@ -46,7 +48,6 @@ class BaseAPIClient:
|
|
46
48
|
return headers
|
47
49
|
|
48
50
|
def _get(self, path: str, params: dict = None, data=None, return_raw=False) -> dict:
|
49
|
-
|
50
51
|
url = f"{self.base_url}{path}"
|
51
52
|
response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
|
52
53
|
self._check_response(response)
|
@@ -1,11 +1,12 @@
|
|
1
1
|
from typing import List
|
2
2
|
|
3
|
+
from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
|
3
4
|
from pydantic import BaseModel, ValidationError
|
4
5
|
from typing import Optional
|
5
6
|
|
6
7
|
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
7
8
|
from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
|
8
|
-
from ibm_watsonx_orchestrate.client.utils import is_cpd_env
|
9
|
+
from ibm_watsonx_orchestrate.client.utils import is_cpd_env, is_local_dev
|
9
10
|
|
10
11
|
import logging
|
11
12
|
logger = logging.getLogger(__name__)
|
@@ -42,6 +43,12 @@ class GetConnectionResponse(BaseModel):
|
|
42
43
|
|
43
44
|
|
44
45
|
class ConnectionsClient(BaseAPIClient):
|
46
|
+
def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, verify: str = None, authenticator: MCSPAuthenticator = None):
|
47
|
+
super(ConnectionsClient, self).__init__(base_url, api_key, is_local, verify, authenticator)
|
48
|
+
if is_local_dev(base_url):
|
49
|
+
self.base_url = f"{base_url.rstrip('/')}/api/v1/orchestrate"
|
50
|
+
else:
|
51
|
+
self.base_url = f"{base_url.rstrip('/')}/v1/orchestrate"
|
45
52
|
"""
|
46
53
|
Client to handle CRUD operations for Connections endpoint
|
47
54
|
"""
|
@@ -77,6 +84,8 @@ class ConnectionsClient(BaseAPIClient):
|
|
77
84
|
else f"/connections/applications?include_details=true"
|
78
85
|
)
|
79
86
|
res = self._get(path)
|
87
|
+
import json
|
88
|
+
json.dumps(res)
|
80
89
|
return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
|
81
90
|
except ValidationError as e:
|
82
91
|
logger.error("Recieved unexpected response from server")
|
@@ -107,25 +116,25 @@ class ConnectionsClient(BaseAPIClient):
|
|
107
116
|
|
108
117
|
# POST /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
109
118
|
# POST /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
110
|
-
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict,
|
111
|
-
if
|
119
|
+
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
|
120
|
+
if use_app_credentials:
|
112
121
|
self._post(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
113
122
|
else:
|
114
123
|
self._post(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
115
124
|
|
116
125
|
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
117
126
|
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
118
|
-
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict,
|
119
|
-
if
|
127
|
+
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
|
128
|
+
if use_app_credentials:
|
120
129
|
self._patch(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
121
130
|
else:
|
122
131
|
self._patch(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
123
132
|
|
124
133
|
# GET /api/v1/connections/applications/{app_id}/configs/credentials?env={env}
|
125
134
|
# GET /api/v1/connections/applications/{app_id}/configs/runtime_credentials?env={env}
|
126
|
-
def get_credentials(self, app_id: str, env: ConnectionEnvironment,
|
135
|
+
def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> dict:
|
127
136
|
try:
|
128
|
-
if
|
137
|
+
if use_app_credentials:
|
129
138
|
path = (
|
130
139
|
f"/connections/applications/{app_id}/credentials?env={env}"
|
131
140
|
if is_cpd_env(self.base_url)
|
@@ -146,8 +155,8 @@ class ConnectionsClient(BaseAPIClient):
|
|
146
155
|
|
147
156
|
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
148
157
|
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
149
|
-
def delete_credentials(self, app_id: str, env: ConnectionEnvironment,
|
150
|
-
if
|
158
|
+
def delete_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> None:
|
159
|
+
if use_app_credentials:
|
151
160
|
self._delete(f"/connections/applications/{app_id}/configs/{env}/credentials")
|
152
161
|
else:
|
153
162
|
self._delete(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials")
|
@@ -17,14 +17,16 @@ def _get_connections_manager_url() -> str:
|
|
17
17
|
url_parts = url.split(":")
|
18
18
|
url_parts[-1] = str(LOCAL_CONNECTION_MANAGER_PORT)
|
19
19
|
url = ":".join(url_parts)
|
20
|
-
url = url + "/api/v1/orchestrate"
|
21
20
|
return url
|
22
21
|
return None
|
23
22
|
|
24
23
|
def get_connections_client() -> ConnectionsClient:
|
25
24
|
return instantiate_client(client=ConnectionsClient, url=_get_connections_manager_url())
|
26
25
|
|
27
|
-
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType:
|
26
|
+
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType | None:
|
27
|
+
if security_scheme is None and auth_type is None:
|
28
|
+
return None
|
29
|
+
|
28
30
|
if security_scheme != ConnectionSecurityScheme.OAUTH2:
|
29
31
|
return ConnectionType(security_scheme)
|
30
32
|
return ConnectionType(auth_type)
|
@@ -14,7 +14,7 @@ DEFAULT_TENANT = {
|
|
14
14
|
DEFAULT_USER = {"username": "wxo.archer@ibm.com", "password": "watsonx"}
|
15
15
|
DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
|
16
16
|
DEFAULT_LOCAL_AUTH_ENDPOINT = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/auth/token"
|
17
|
-
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/tenants"
|
17
|
+
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/tenants"
|
18
18
|
DEFAULT_LOCAL_TENANT_AUTH_ENDPOINT = "{}/api/v1/auth/token?tenant_id={}"
|
19
19
|
|
20
20
|
|
@@ -51,13 +51,13 @@ class ServiceInstance(BaseServiceInstance):
|
|
51
51
|
def _create_token(self) -> str:
|
52
52
|
if not self._credentials.auth_type:
|
53
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 '
|
54
|
+
logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
|
55
55
|
return self._authenticate(EnvironmentAuthType.IBM_CLOUD_IAM)
|
56
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 '
|
57
|
+
logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
|
58
58
|
return self._authenticate(EnvironmentAuthType.CPD)
|
59
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 '
|
60
|
+
logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd' ")
|
61
61
|
return self._authenticate(EnvironmentAuthType.MCSP)
|
62
62
|
else:
|
63
63
|
return self._authenticate(self._credentials.auth_type)
|
@@ -29,12 +29,17 @@ class TempusClient(BaseAPIClient):
|
|
29
29
|
authenticator=authenticator
|
30
30
|
)
|
31
31
|
|
32
|
+
def get_tempus_endpoint(self) -> str:
|
33
|
+
"""
|
34
|
+
Returns the Tempus endpoint URL
|
35
|
+
"""
|
36
|
+
return self.base_url
|
32
37
|
def create_update_flow_model(self, flow_id: str, model: dict) -> dict:
|
33
|
-
return self._post(f"/
|
38
|
+
return self._post(f"/flow-models/{flow_id}", data=model)
|
34
39
|
|
35
40
|
def run_flow(self, flow_id: str, input: dict) -> dict:
|
36
|
-
return self._post(f"/
|
41
|
+
return self._post(f"/flows/{flow_id}/versions/TIP/run", data=input)
|
37
42
|
|
38
43
|
def arun_flow(self, flow_id: str, input: dict) -> dict:
|
39
|
-
return self._post(f"/
|
44
|
+
return self._post(f"/flows/{flow_id}/versions/TIP/run/async", data=input)
|
40
45
|
|
@@ -47,6 +47,16 @@ def is_local_dev(url: str | None = None) -> bool:
|
|
47
47
|
|
48
48
|
return False
|
49
49
|
|
50
|
+
def is_ibm_cloud():
|
51
|
+
cfg = Config()
|
52
|
+
active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
53
|
+
url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
|
54
|
+
|
55
|
+
if url.__contains__("cloud.ibm.com"):
|
56
|
+
return True
|
57
|
+
return False
|
58
|
+
|
59
|
+
|
50
60
|
def is_cpd_env(url: str) -> bool:
|
51
61
|
if url.lower().startswith("https://cpd"):
|
52
62
|
return True
|