ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.8.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.
Files changed (57) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
  3. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
  4. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
  5. ibm_watsonx_orchestrate/agent_builder/agents/types.py +26 -9
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +1 -0
  7. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +25 -10
  8. ibm_watsonx_orchestrate/agent_builder/connections/types.py +5 -9
  9. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
  10. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
  11. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
  13. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
  14. ibm_watsonx_orchestrate/agent_builder/tools/types.py +10 -9
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +35 -7
  17. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +33 -23
  18. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  19. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -4
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  21. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +293 -0
  22. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +154 -0
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +2 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
  28. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +33 -19
  29. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  30. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +66 -9
  31. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
  33. ibm_watsonx_orchestrate/cli/config.py +3 -3
  34. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  35. ibm_watsonx_orchestrate/cli/main.py +5 -0
  36. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  37. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +66 -0
  38. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  39. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  40. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  41. ibm_watsonx_orchestrate/client/utils.py +48 -9
  42. ibm_watsonx_orchestrate/docker/compose-lite.yml +16 -4
  43. ibm_watsonx_orchestrate/docker/default.env +25 -15
  44. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +3 -1
  45. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  46. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  47. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +91 -20
  48. ibm_watsonx_orchestrate/flow_builder/node.py +12 -1
  49. ibm_watsonx_orchestrate/flow_builder/types.py +169 -16
  50. ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
  51. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  52. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/METADATA +5 -5
  53. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/RECORD +56 -52
  54. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  55. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/WHEEL +0 -0
  56. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/entry_points.txt +0 -0
  57. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -41,7 +41,7 @@ from ibm_watsonx_orchestrate.client.connections import get_connections_client, g
41
41
  from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
42
42
  from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_support_tools
43
43
  from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
44
- from ibm_watsonx_orchestrate.client.utils import is_local_dev
44
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
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 = {conn.app_id:conn for conn in imported_connections_list}
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
- if kind == ToolKind.openapi and imported_connections.get(app_id).security_scheme == ConnectionSecurityScheme.KEY_VALUE:
93
- logger.error(f"Key value application connections can not be bound to an openapi tool")
94
- exit(1)
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 = {conn.connection_id:conn for conn in imported_connections_list}
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
- if imported_connection and len(expected_tool_conn_types) and imported_connection_auth_type not in expected_tool_conn_types:
196
- logger.error(f"The app-id '{imported_connection.app_id}' is of type '{imported_connection_auth_type.value}'. 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")
197
- validation_failed = True
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,7 +400,14 @@ 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]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].
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].
404
+
405
+ [bold cyan]Experimental Features - Scheduling Flows and Agents: [/bold cyan]
406
+ - You can now schedule any Flows to be run on a later time. Just include the [bold green]"schedulable=True"[/bold green] attribute in the @flow decorator.
407
+ - Once enabled, you can schedule a flow by saying something like: [bold green]Can you schedule the flow <flow_name> to run everyday at 7am EST for 3 times?[/bold green]
408
+ - To schedule an agent, see the example in [bold green]examples/flow_builder/agent_scheduler[/bold green]. Use that to import the [bold green]agent_run[/bold green] tool to your agent.
409
+ - Use [bold green]agent_run[/bold green] tool to schedule an agent. For example: [bold green]Can you schedule the agent <agent_name> to run every weekday at 8am UK time?[/bold green]
410
+ - In scheduling, it is important to mention timezone or UTC time (also known as Greenwich Mean Time or Coordinated Universal Time) will be used.
349
411
 
350
412
  """
351
413
 
@@ -422,7 +484,7 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
422
484
  permission="read_only",
423
485
  flow_model=model)
424
486
 
425
- tools = import_flow_support_tools()
487
+ tools = import_flow_support_tools(model=model)
426
488
 
427
489
  tools.append(tool)
428
490
 
@@ -499,7 +561,7 @@ class ToolsController:
499
561
  tools = []
500
562
  logger.warning("Skill Import not implemented yet")
501
563
  case _:
502
- raise ValueError("Invalid kind selected")
564
+ raise BadRequest("Invalid kind selected")
503
565
 
504
566
  for tool in tools:
505
567
  yield tool
@@ -507,7 +569,20 @@ class ToolsController:
507
569
 
508
570
  def list_tools(self, verbose=False):
509
571
  response = self.get_client().get()
510
- tool_specs = [ToolSpec.model_validate(tool) for tool in response]
572
+ tool_specs = []
573
+ parse_errors = []
574
+
575
+ for tool in response:
576
+ try:
577
+ tool_specs.append(ToolSpec.model_validate(tool))
578
+ except Exception as e:
579
+ name = tool.get('name', None)
580
+ parse_errors.append([
581
+ f"Tool '{name}' could not be parsed",
582
+ json.dumps(tool),
583
+ e
584
+ ])
585
+
511
586
  tools = [BaseTool(spec=spec) for spec in tool_specs]
512
587
 
513
588
  if verbose:
@@ -596,6 +671,10 @@ class ToolsController:
596
671
 
597
672
  rich.print(table)
598
673
 
674
+ for error in parse_errors:
675
+ for l in error:
676
+ logger.error(l)
677
+
599
678
  def get_all_tools(self) -> dict:
600
679
  return {entry["name"]: entry["id"] for entry in self.get_client().get()}
601
680
 
@@ -6,6 +6,7 @@ from copy import deepcopy
6
6
  from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
7
7
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
8
8
  from enum import Enum
9
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
9
10
 
10
11
  # Section Headers
11
12
  AUTH_SECTION_HEADER = "auth"
@@ -82,7 +83,6 @@ def _check_if_default_config_file(folder, file):
82
83
  def _check_if_auth_config_file(folder, file):
83
84
  return folder == AUTH_CONFIG_FILE_FOLDER and file == AUTH_CONFIG_FILE
84
85
 
85
-
86
86
  def clear_protected_env_credentials_token():
87
87
  auth_cfg = Config(config_file_folder=AUTH_CONFIG_FILE_FOLDER, config_file=AUTH_CONFIG_FILE)
88
88
  auth_cfg.delete(AUTH_SECTION_HEADER, PROTECTED_ENV_NAME, AUTH_MCSP_TOKEN_OPT)
@@ -91,7 +91,7 @@ def clear_protected_env_credentials_token():
91
91
  class ConfigFileTypes(str, Enum):
92
92
  AUTH = 'auth'
93
93
  CONFIG = 'config'
94
-
94
+ DOCPROC_FEATURE_CONF= 'docproc_feature'
95
95
 
96
96
  class Config:
97
97
 
@@ -209,7 +209,7 @@ class Config:
209
209
  as keys to access deeper sections of the config and then deleting the last specified key.
210
210
  """
211
211
  if len(args) < 1:
212
- raise ValueError("Config.delete() requires at least one positional argument")
212
+ raise BadRequest("Config.delete() requires at least one positional argument")
213
213
 
214
214
  config_data = {}
215
215
  try:
@@ -4,6 +4,7 @@ from typing import Optional
4
4
  from rich import print as pprint
5
5
  from dotenv import dotenv_values
6
6
  import typer
7
+ import sys
7
8
 
8
9
  from ibm_watsonx_orchestrate.cli.config import Config, PYTHON_REGISTRY_HEADER, \
9
10
  PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT
@@ -29,7 +30,6 @@ def version_callback(checkVersion: bool=True):
29
30
 
30
31
  raise typer.Exit()
31
32
 
32
-
33
33
 
34
34
  def init_callback(
35
35
  ctx: typer.Context,
@@ -38,6 +38,15 @@ def init_callback(
38
38
  "--version",
39
39
  help="Show the installed version of the ADK and Developer Edition Tags",
40
40
  callback=version_callback
41
+ ),
42
+ debug: Optional[bool] = typer.Option(
43
+ False,
44
+ "--debug",
45
+ help="Enable debug mode"
41
46
  )
42
47
  ):
48
+ if debug:
49
+ sys.tracebacklimit = 40
50
+ else:
51
+ sys.tracebacklimit = 0
43
52
  pass
@@ -1,4 +1,6 @@
1
+
1
2
  import typer
3
+ import sys
2
4
 
3
5
  from ibm_watsonx_orchestrate.cli.commands.connections.connections_command import connections_app
4
6
  from ibm_watsonx_orchestrate.cli.commands.login.login_command import login_app
@@ -13,6 +15,7 @@ from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import chann
13
15
  from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
14
16
  from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
15
17
  from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_command import evaluation_app
18
+ from ibm_watsonx_orchestrate.cli.commands.copilot.copilot_command import copilot_app
16
19
  from ibm_watsonx_orchestrate.cli.init_helper import init_callback
17
20
 
18
21
  import urllib3
@@ -24,6 +27,7 @@ app = typer.Typer(
24
27
  pretty_exceptions_enable=False,
25
28
  callback=init_callback
26
29
  )
30
+
27
31
  app.add_typer(login_app)
28
32
  app.add_typer(environment_app, name="env", help='Add, remove, or select the activate env other commands will interact with (either your local server or a production instance)')
29
33
  app.add_typer(agents_app, name="agents", help='Interact with the agents in your active env')
@@ -36,6 +40,7 @@ app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Dev
36
40
  app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
37
41
  app.add_typer(channel_app, name="channels", help="Configure channels where your agent can exist on (such as embedded webchat)")
38
42
  app.add_typer(evaluation_app, name="evaluations", help='Evaluate the performance of your agents in your active env')
43
+ app.add_typer(copilot_app, name="copilot", help='Access AI powered assistance to help refine your agents')
39
44
  app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
40
45
 
41
46
  if __name__ == "__main__":
@@ -3,6 +3,7 @@ import json
3
3
  import requests
4
4
  from abc import ABC, abstractmethod
5
5
  from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
6
+ from typing_extensions import List
6
7
 
7
8
 
8
9
  class ClientAPIException(requests.HTTPError):
@@ -62,6 +63,17 @@ class BaseAPIClient:
62
63
  self._check_response(response)
63
64
  return response.json() if response.text else {}
64
65
 
66
+ def _post_nd_json(self, path: str, data: dict = None, files: dict = None) -> List[dict]:
67
+ url = f"{self.base_url}{path}"
68
+ response = requests.post(url, headers=self._get_headers(), json=data, files=files)
69
+ self._check_response(response)
70
+
71
+ res = []
72
+ if response.text:
73
+ for line in response.text.splitlines():
74
+ res.append(json.loads(line))
75
+ return res
76
+
65
77
  def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
66
78
  url = f"{self.base_url}{path}"
67
79
  # Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
@@ -0,0 +1,66 @@
1
+ from typing import Dict, Any
2
+ from uuid import uuid4
3
+
4
+ from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
5
+
6
+
7
+ class CPEClient(BaseAPIClient):
8
+ """
9
+ Client to handle CRUD operations for Conversational Prompt Engineering Service
10
+ """
11
+
12
+ def __init__(self, *args, **kwargs):
13
+ self.chat_id = str(uuid4())
14
+ super().__init__(*args, **kwargs)
15
+ self.base_url = kwargs.get("base_url", self.base_url)
16
+ self.chat_model_name = 'llama-3-3-70b-instruct'
17
+
18
+ def _get_headers(self) -> dict:
19
+ return {
20
+ "chat_id": self.chat_id
21
+ }
22
+
23
+
24
+ def submit_pre_cpe_chat(self, user_message: str | None =None, tools: Dict[str, Any] = None) -> dict:
25
+ payload = {
26
+ "message": user_message,
27
+ "tools": tools,
28
+ "chat_id": self.chat_id,
29
+ "chat_model_name": self.chat_model_name
30
+ }
31
+
32
+ response = self._post_nd_json("/wxo-cpe/create-agent", data=payload)
33
+
34
+ if response:
35
+ return response[-1]
36
+
37
+
38
+ def init_with_context(self, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
39
+ payload = {
40
+ "context_data": context_data,
41
+ "chat_id": self.chat_id
42
+ }
43
+
44
+ if model:
45
+ payload["target_model_name"] = model
46
+
47
+ response = self._post_nd_json("/wxo-cpe/init_cpe_from_wxo", data=payload)
48
+
49
+ if response:
50
+ return response[-1]
51
+
52
+
53
+ def invoke(self, prompt: str, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
54
+ payload = {
55
+ "prompt": prompt,
56
+ "context_data": context_data,
57
+ "chat_id": self.chat_id
58
+ }
59
+
60
+ if model:
61
+ payload["target_model_name"] = model
62
+
63
+ response = self._post_nd_json("/wxo-cpe/invoke", data=payload)
64
+
65
+ if response:
66
+ return response[-1]
@@ -1,4 +1,4 @@
1
- from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
1
+ from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
2
2
  import json
3
3
  from typing_extensions import List
4
4
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
@@ -2,6 +2,8 @@ from ibm_watsonx_orchestrate.client.base_service_instance import BaseServiceInst
2
2
  import logging
3
3
  import requests
4
4
  from ibm_watsonx_orchestrate.client.credentials import Credentials
5
+ import json
6
+ import base64
5
7
 
6
8
  logger = logging.getLogger(__name__)
7
9
 
@@ -11,7 +13,7 @@ DEFAULT_TENANT = {
11
13
  "tags": ["test"]
12
14
  }
13
15
 
14
- DEFAULT_USER = {"username": "wxo.archer@ibm.com", "password": "watsonx"}
16
+ DEFAULT_USER = json.loads(base64.b64decode('eyJ1c2VybmFtZSI6ICJ3eG8uYXJjaGVyQGlibS5jb20iLCJwYXNzd29yZCI6ICJ3YXRzb254In0='))
15
17
  DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
16
18
  DEFAULT_LOCAL_AUTH_ENDPOINT = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/auth/token"
17
19
  DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/tenants"
@@ -6,6 +6,7 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
9
+ from ibm_cloud_sdk_core.authenticators import MCSPV2Authenticator
9
10
  from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
10
11
  from ibm_cloud_sdk_core.authenticators import CloudPakForDataAuthenticator
11
12
 
@@ -51,24 +52,47 @@ class ServiceInstance(BaseServiceInstance):
51
52
  def _create_token(self) -> str:
52
53
  if not self._credentials.auth_type:
53
54
  if ".cloud.ibm.com" in self._credentials.url:
54
- logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
55
+ logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'mcsp', 'mcsp_v1', 'mcsp_v2' or 'cpd' ")
55
56
  return self._authenticate(EnvironmentAuthType.IBM_CLOUD_IAM)
56
57
  elif is_cpd_env(self._credentials.url):
57
- logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
58
+ logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam', 'mcsp', 'mcsp_v1' or 'mcsp_v2' ")
58
59
  return self._authenticate(EnvironmentAuthType.CPD)
59
60
  else:
60
- logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd' ")
61
- return self._authenticate(EnvironmentAuthType.MCSP)
61
+ logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam', 'mcsp_v1', 'mcsp_v2' or 'cpd' ")
62
+ try:
63
+ return self._authenticate(EnvironmentAuthType.MCSP_V1)
64
+ except:
65
+ return self._authenticate(EnvironmentAuthType.MCSP_V2)
66
+ auth_type = self._credentials.auth_type.lower()
67
+ if auth_type == "mcsp":
68
+ try:
69
+ return self._authenticate(EnvironmentAuthType.MCSP_V1)
70
+ except:
71
+ return self._authenticate(EnvironmentAuthType.MCSP_V2)
72
+ elif auth_type == "mcsp_v1":
73
+ return self._authenticate(EnvironmentAuthType.MCSP_V1)
74
+ elif auth_type == "mcsp_v2":
75
+ return self._authenticate(EnvironmentAuthType.MCSP_V2)
62
76
  else:
63
- return self._authenticate(self._credentials.auth_type)
77
+ return self._authenticate(auth_type)
64
78
 
65
79
  def _authenticate(self, auth_type: str) -> str:
66
80
  """Handles authentication based on the auth_type."""
67
81
  try:
68
82
  match auth_type:
69
- case EnvironmentAuthType.MCSP:
83
+ case EnvironmentAuthType.MCSP | EnvironmentAuthType.MCSP_V1:
70
84
  url = self._credentials.iam_url if self._credentials.iam_url is not None else "https://iam.platform.saas.ibm.com"
71
85
  authenticator = MCSPAuthenticator(apikey=self._credentials.api_key, url=url)
86
+ case EnvironmentAuthType.MCSP_V2:
87
+ url = self._credentials.iam_url if self._credentials.iam_url is not None else "https://account-iam.platform.saas.ibm.com"
88
+ wxo_url = self._credentials.url
89
+ instance_id = wxo_url.split("instances/")[1]
90
+ authenticator = MCSPV2Authenticator(
91
+ apikey=self._credentials.api_key,
92
+ url=url,
93
+ scope_collection_type="services",
94
+ scope_id=instance_id
95
+ )
72
96
  case EnvironmentAuthType.IBM_CLOUD_IAM:
73
97
  authenticator = IAMAuthenticator(apikey=self._credentials.api_key, url=self._credentials.iam_url)
74
98
  case EnvironmentAuthType.CPD:
@@ -100,8 +124,10 @@ class ServiceInstance(BaseServiceInstance):
100
124
  raise ClientError(f"Unsupported authentication type: {auth_type}")
101
125
 
102
126
  return authenticator.token_manager.get_token()
127
+
103
128
  except Exception as e:
104
- raise ClientError(f"Error getting {auth_type.upper()} Token", e)
129
+ raise ClientError(f"Error getting {auth_type.upper()} Token", logg_messages=False)
130
+
105
131
 
106
132
 
107
133
  def _is_token_refresh_possible(self) -> bool:
@@ -21,17 +21,32 @@ from typing import TypeVar
21
21
  import os
22
22
  import jwt
23
23
  import time
24
+ import sys
24
25
 
25
26
  logger = logging.getLogger(__name__)
26
27
  LOCK = Lock()
27
28
  T = TypeVar("T", bound=BaseAPIClient)
28
29
 
30
+ def get_current_env_url() -> str:
31
+ cfg = Config()
32
+ active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
33
+ return cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
34
+
35
+ def get_cpd_instance_id_from_url(url: str | None = None) -> str:
36
+ if url is None:
37
+ url = get_current_env_url()
38
+
39
+ if not is_cpd_env(url):
40
+ logger.error(f"The host {url} is not a CPD instance")
41
+ sys.exit(1)
42
+
43
+ url_fragments = url.split('/')
44
+ return url_fragments[-1] if url_fragments[-1] else url_fragments[-2]
45
+
29
46
 
30
47
  def is_local_dev(url: str | None = None) -> bool:
31
48
  if url is None:
32
- cfg = Config()
33
- active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
34
- url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
49
+ url = get_current_env_url()
35
50
 
36
51
  if url.startswith("http://localhost"):
37
52
  return True
@@ -47,21 +62,45 @@ def is_local_dev(url: str | None = None) -> bool:
47
62
 
48
63
  return False
49
64
 
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)
65
+ def is_cpd_env(url: str | None = None) -> bool:
66
+ if url is None:
67
+ url = get_current_env_url()
68
+
69
+ if url.lower().startswith("https://cpd"):
70
+ return True
71
+ return False
72
+
73
+ def is_saas_env():
74
+ return is_ga_platform() or is_ibm_cloud_platform()
75
+
76
+ def is_ibm_cloud_platform(url:str | None = None) -> bool:
77
+ if url is None:
78
+ url = get_current_env_url()
54
79
 
55
80
  if url.__contains__("cloud.ibm.com"):
56
81
  return True
57
82
  return False
58
83
 
84
+ def is_ga_platform(url: str | None = None) -> bool:
85
+ if url is None:
86
+ url = get_current_env_url()
59
87
 
60
- def is_cpd_env(url: str) -> bool:
61
- if url.lower().startswith("https://cpd"):
88
+ if url.__contains__("orchestrate.ibm.com"):
62
89
  return True
63
90
  return False
64
91
 
92
+
93
+ def get_environment() -> str:
94
+ if is_local_dev():
95
+ return "local"
96
+ if is_cpd_env():
97
+ return "cpd"
98
+ if is_ibm_cloud_platform():
99
+ return "ibmcloud"
100
+ if is_ga_platform():
101
+ return "ga"
102
+ return None
103
+
65
104
  def check_token_validity(token: str) -> bool:
66
105
  try:
67
106
  token_claimset = jwt.decode(token, options={"verify_signature": False})
@@ -120,7 +120,7 @@ services:
120
120
  TEMPUS_RUNTIME_ENDPOINT: http://wxo-tempus-runtime:9044
121
121
  CONNECTIONS_MANAGER_ENDPOINT: http://wxo-server-connection-manager:3001
122
122
  AGENT_OPS_API_KEY: ${AGENTOPS_API_KEY}
123
- AGENTS_OPS_RUNTIME_ENDPOINT: https://host.docker.internal:8765
123
+ AGENTS_OPS_RUNTIME_ENDPOINT: https://frontend-server:443
124
124
  IS_OBSERVABILITY_FEATURE_ENABLED: "true"
125
125
  ALLOW_INSECURE_TLS: "true"
126
126
  command: 'npm start'
@@ -751,8 +751,8 @@ services:
751
751
  SERVER_HTTP_PORT: "9044"
752
752
  SERVER_CONTEXT_PATH:
753
753
  SERVER_JWK_URL: https://wo-ibm-dev.verify.ibm.com/v1.0/endpoint/default/jwks
754
- SERVER_STANDALONE_TASK_CALLBACK_URL: http://localhost:9044/task/callback/{thread_id}/{requestId}
755
- SERVER_FLOW_CALLBACK_URL: http://localhost:9044/v1/flows/{flow_instance_id}/{task_instance_id}/resume/async
754
+ SERVER_STANDALONE_TASK_CALLBACK_URL: http://wxo-tempus-runtime:9044/task/callback/{thread_id}/{requestId}
755
+ SERVER_FLOW_CALLBACK_URL: http://wxo-tempus-runtime:9044/v1/flows/{flow_instance_id}/{task_instance_id}/resume/async
756
756
  JWT_SECRET: ${JWT_SECRET}
757
757
  SERVER_INTERNAL_PROTOCOL: http
758
758
  SERVER_INTERNAL_HOSTNAME: wxo-tempus-runtime
@@ -782,6 +782,7 @@ services:
782
782
  LOG_LEVEL: info
783
783
  DISABLE_FLOW_BINDING: true
784
784
  DOCPROC_ENABLED: ${DOCPROC_ENABLED:-false}
785
+ DOCPROC_BASE_URL: http://wxo-doc-processing-infra-standalone:9080
785
786
  WO_API_KEY: ${WO_API_KEY}
786
787
  WO_INSTANCE: ${WO_INSTANCE}
787
788
  AUTHORIZATION_URL: ${AUTHORIZATION_URL}
@@ -795,6 +796,16 @@ services:
795
796
  - 9044:9044
796
797
  depends_on:
797
798
  - wxo-server-db
799
+
800
+ cpe:
801
+ image: ${CPE_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-prompt-optimizer:${CPE_TAG:-latest}
802
+ platform: linux/amd64
803
+ restart: unless-stopped
804
+ environment:
805
+ WATSONX_APIKEY: ${WATSONX_APIKEY}
806
+ WATSONX_SPACE_ID: ${WATSONX_SPACE_ID}
807
+ ports:
808
+ - 8081:8080
798
809
 
799
810
  ########################
800
811
  # DOCPROC dependencies
@@ -863,7 +874,7 @@ services:
863
874
  condition: service_completed_successfully
864
875
 
865
876
  wdu-model-copy:
866
- image: ${WDU_REGISTRY:-us.icr.io/watson-orchestrate-private}/document-processing/wdu-runtime:${WDU_TAG:-2.4.0}
877
+ image: ${WDU_REGISTRY:-us.icr.io/watson-orchestrate-private}/document-processing/wdu-models:${WDU_TAG:-2.4.0}
867
878
  platform: linux/amd64
868
879
  user: root
869
880
  profiles:
@@ -902,6 +913,7 @@ services:
902
913
  DPI_WO_WDU_SERVER_ENDPOINT: https://wxo-doc-processing-service:8080
903
914
  # DPI_RAG_SERVER_ENDPOINT: https://wxo-doc-processing-llm-service:8083
904
915
  DISABLE_TLS: true
916
+ STANDALONE_DPI: "true"
905
917
  depends_on:
906
918
  wxo-doc-processing-dpi-minio-init:
907
919
  condition: service_completed_successfully