ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) 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 +38 -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 +19 -11
  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/types.py +15 -2
  18. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +35 -25
  19. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  20. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  21. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +12 -12
  22. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  23. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
  24. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
  25. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
  26. ibm_watsonx_orchestrate/cli/commands/environment/types.py +3 -1
  27. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +134 -36
  28. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +42 -11
  29. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
  30. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +36 -20
  31. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
  33. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +59 -10
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
  36. ibm_watsonx_orchestrate/cli/config.py +3 -3
  37. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  38. ibm_watsonx_orchestrate/cli/main.py +5 -0
  39. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  40. ibm_watsonx_orchestrate/client/connections/connections_client.py +5 -30
  41. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
  42. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  43. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  44. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  45. ibm_watsonx_orchestrate/client/utils.py +49 -8
  46. ibm_watsonx_orchestrate/docker/compose-lite.yml +25 -6
  47. ibm_watsonx_orchestrate/docker/default.env +26 -15
  48. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +9 -4
  49. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  50. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  51. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +131 -20
  52. ibm_watsonx_orchestrate/flow_builder/node.py +18 -1
  53. ibm_watsonx_orchestrate/flow_builder/types.py +271 -15
  54. ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
  55. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  56. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/METADATA +5 -5
  57. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/RECORD +60 -56
  58. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  59. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/WHEEL +0 -0
  60. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/entry_points.txt +0 -0
  61. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -33,6 +33,21 @@ logger = logging.getLogger(__name__)
33
33
  server_app = typer.Typer(no_args_is_help=True)
34
34
 
35
35
 
36
+ _ALWAYS_UNSET: set[str] = {
37
+ "WO_API_KEY",
38
+ "WO_INSTANCE",
39
+ "DOCKER_IAM_KEY",
40
+ "WO_DEVELOPER_EDITION_SOURCE",
41
+ "WATSONX_SPACE_ID",
42
+ "WATSONX_APIKEY",
43
+ "WO_USERNAME",
44
+ "WO_PASSWORD",
45
+ }
46
+
47
+ def define_saas_wdu_runtime(value: str = "none") -> None:
48
+ cfg = Config()
49
+ cfg.write(USER_ENV_CACHE_HEADER,"SAAS_WDU_RUNTIME",value)
50
+
36
51
  def ensure_docker_installed() -> None:
37
52
  try:
38
53
  subprocess.run(["docker", "--version"], check=True, capture_output=True)
@@ -261,6 +276,13 @@ def _check_exclusive_observibility(langfuse_enabled: bool, ibm_tele_enabled: boo
261
276
  return False
262
277
  return True
263
278
 
279
+ def _prepare_clean_env(env_file: Path) -> None:
280
+ """Remove env vars so terminal definitions don't override"""
281
+ keys_from_file = set(dotenv_values(str(env_file)).keys())
282
+ keys_to_unset = keys_from_file | _ALWAYS_UNSET
283
+ for key in keys_to_unset:
284
+ os.environ.pop(key, None)
285
+
264
286
  def write_merged_env_file(merged_env: dict) -> Path:
265
287
  tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
266
288
  with tmp:
@@ -293,7 +315,8 @@ NON_SECRET_ENV_ITEMS = {
293
315
  "WO_INSTANCE",
294
316
  "USE_SAAS_ML_TOOLS_RUNTIME",
295
317
  "AUTHORIZATION_URL",
296
- "OPENSOURCE_REGISTRY_PROXY"
318
+ "OPENSOURCE_REGISTRY_PROXY",
319
+ "SAAS_WDU_RUNTIME"
297
320
  }
298
321
  def persist_user_env(env: dict, include_secrets: bool = False) -> None:
299
322
  if include_secrets:
@@ -313,9 +336,10 @@ def get_persisted_user_env() -> dict | None:
313
336
  user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
314
337
  return user_env
315
338
 
316
- def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_docproc=False) -> None:
339
+ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_doc_processing=False) -> None:
317
340
  compose_path = get_compose_file()
318
341
  compose_command = ensure_docker_compose_installed()
342
+ _prepare_clean_env(final_env_file)
319
343
  db_tag = read_env_file(final_env_file).get('DBTAG', None)
320
344
  logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
321
345
 
@@ -345,7 +369,7 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
345
369
  profiles.append("langfuse")
346
370
  if experimental_with_ibm_telemetry:
347
371
  profiles.append("ibm-telemetry")
348
- if with_docproc:
372
+ if with_doc_processing:
349
373
  profiles.append("docproc")
350
374
 
351
375
  command = compose_command[:]
@@ -358,6 +382,8 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
358
382
  "up",
359
383
  "--scale",
360
384
  "ui=0",
385
+ "--scale",
386
+ "cpe=0",
361
387
  "-d",
362
388
  "--remove-orphans",
363
389
  ]
@@ -428,6 +454,7 @@ def wait_for_wxo_ui_health_check(timeout_seconds=45, interval_seconds=2):
428
454
  def run_compose_lite_ui(user_env_file: Path) -> bool:
429
455
  compose_path = get_compose_file()
430
456
  compose_command = ensure_docker_compose_installed()
457
+ _prepare_clean_env(user_env_file)
431
458
  ensure_docker_installed()
432
459
 
433
460
  default_env = read_env_file(get_default_env_file())
@@ -522,6 +549,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
522
549
  def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> None:
523
550
  compose_path = get_compose_file()
524
551
  compose_command = ensure_docker_compose_installed()
552
+ _prepare_clean_env(user_env_file)
525
553
 
526
554
 
527
555
  ensure_docker_installed()
@@ -565,6 +593,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
565
593
  def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
566
594
  compose_path = get_compose_file()
567
595
  compose_command = ensure_docker_compose_installed()
596
+ _prepare_clean_env(final_env_file)
568
597
 
569
598
  command = compose_command + [
570
599
  '--profile', '*',
@@ -597,6 +626,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
597
626
  def run_compose_lite_logs(final_env_file: Path, is_reset: bool = False) -> None:
598
627
  compose_path = get_compose_file()
599
628
  compose_command = ensure_docker_compose_installed()
629
+ _prepare_clean_env(final_env_file)
600
630
 
601
631
  command = compose_command + [
602
632
  "-f", str(compose_path),
@@ -752,14 +782,16 @@ def server_start(
752
782
  "--accept-terms-and-conditions",
753
783
  help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
754
784
  ),
755
- with_docproc: bool = typer.Option(
785
+ with_doc_processing: bool = typer.Option(
756
786
  False,
757
- '--with-docproc', '-d',
787
+ '--with-doc-processing', '-d',
758
788
  help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
759
789
  ),
760
790
  ):
761
791
  confirm_accepts_license_agreement(accept_terms_and_conditions)
762
792
 
793
+ define_saas_wdu_runtime()
794
+
763
795
  if user_env_file and not Path(user_env_file).exists():
764
796
  logger.error(f"Error: The specified environment file '{user_env_file}' does not exist.")
765
797
  sys.exit(1)
@@ -795,8 +827,9 @@ def server_start(
795
827
  if experimental_with_langfuse:
796
828
  merged_env_dict['LANGFUSE_ENABLED'] = 'true'
797
829
 
798
- if with_docproc:
830
+ if with_doc_processing:
799
831
  merged_env_dict['DOCPROC_ENABLED'] = 'true'
832
+ define_saas_wdu_runtime("local")
800
833
 
801
834
  if experimental_with_ibm_telemetry:
802
835
  merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
@@ -815,8 +848,8 @@ def server_start(
815
848
  run_compose_lite(final_env_file=final_env_file,
816
849
  experimental_with_langfuse=experimental_with_langfuse,
817
850
  experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
818
- with_docproc=with_docproc)
819
-
851
+ with_doc_processing=with_doc_processing)
852
+
820
853
  run_db_migration()
821
854
 
822
855
  logger.info("Waiting for orchestrate server to be fully initialized and ready...")
@@ -843,8 +876,8 @@ def server_start(
843
876
 
844
877
  if experimental_with_langfuse:
845
878
  logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
846
- if with_docproc:
847
- logger.info(f"Document processing capabilities are now available for use in Flows (both ADK and runtime). Note: This option is currently available only in the Developer edition.")
879
+ if with_doc_processing:
880
+ logger.info(f"Document processing in Flows (Public Preview) has been enabled.")
848
881
 
849
882
  @server_app.command(name="stop")
850
883
  def server_stop(
@@ -854,6 +887,7 @@ def server_stop(
854
887
  help="Path to a .env file that overrides default.env. Then environment variables override both."
855
888
  )
856
889
  ):
890
+
857
891
  ensure_docker_installed()
858
892
  default_env_path = get_default_env_file()
859
893
  merged_env_dict = merge_env(
@@ -910,9 +944,24 @@ def server_logs(
910
944
  def run_db_migration() -> None:
911
945
  compose_path = get_compose_file()
912
946
  compose_command = ensure_docker_compose_installed()
947
+ default_env_path = get_default_env_file()
948
+ merged_env_dict = merge_env(default_env_path, user_env_path=None)
949
+ merged_env_dict['WATSONX_SPACE_ID']='X'
950
+ merged_env_dict['WATSONX_APIKEY']='X'
951
+ merged_env_dict['WXAI_API_KEY'] = ''
952
+ merged_env_dict['ASSISTANT_EMBEDDINGS_API_KEY'] = ''
953
+ merged_env_dict['ASSISTANT_LLM_SPACE_ID'] = ''
954
+ merged_env_dict['ROUTING_LLM_SPACE_ID'] = ''
955
+ merged_env_dict['USE_SAAS_ML_TOOLS_RUNTIME'] = ''
956
+ merged_env_dict['BAM_API_KEY'] = ''
957
+ merged_env_dict['ASSISTANT_EMBEDDINGS_SPACE_ID'] = ''
958
+ merged_env_dict['ROUTING_LLM_API_KEY'] = ''
959
+ merged_env_dict['ASSISTANT_LLM_API_KEY'] = ''
960
+ final_env_file = write_merged_env_file(merged_env_dict)
913
961
 
914
962
  command = compose_command + [
915
963
  "-f", str(compose_path),
964
+ "--env-file", str(final_env_file),
916
965
  "exec",
917
966
  "wxo-server-db",
918
967
  "bash",
@@ -47,7 +47,7 @@ def import_toolkit(
47
47
  ] = None,
48
48
  tools: Annotated[
49
49
  Optional[str],
50
- typer.Option("--tools", "-t", help="Comma-separated list of tools to import. Or you can use `*` to use all tools"),
50
+ typer.Option("--tools", "-t", help="Comma-separated list of tools to import. Or you can use \"*\" to use all tools"),
51
51
  ] = None,
52
52
  app_id: Annotated[
53
53
  List[str],
@@ -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
@@ -63,12 +63,7 @@ class ConnectionsClient(BaseAPIClient):
63
63
  # GET /api/v1/connections/applications/{app_id}
64
64
  def get(self, app_id: str) -> GetConnectionResponse | None:
65
65
  try:
66
- path = (
67
- f"/connections/applications/{app_id}"
68
- if is_cpd_env(self.base_url)
69
- else f"/connections/applications?app_id={app_id}"
70
- )
71
- return GetConnectionResponse.model_validate(self._get(path))
66
+ return GetConnectionResponse.model_validate(self._get(f"/connections/applications?app_id={app_id}"))
72
67
  except ClientAPIException as e:
73
68
  if e.response.status_code == 404:
74
69
  return None
@@ -78,12 +73,7 @@ class ConnectionsClient(BaseAPIClient):
78
73
  # GET api/v1/connections/applications
79
74
  def list(self) -> List[ListConfigsResponse]:
80
75
  try:
81
- path = (
82
- f"/connections/applications"
83
- if is_cpd_env(self.base_url)
84
- else f"/connections/applications?include_details=true"
85
- )
86
- res = self._get(path)
76
+ res = self._get(f"/connections/applications?include_details=true")
87
77
  import json
88
78
  json.dumps(res)
89
79
  return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
@@ -135,19 +125,9 @@ class ConnectionsClient(BaseAPIClient):
135
125
  def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> dict:
136
126
  try:
137
127
  if use_app_credentials:
138
- path = (
139
- f"/connections/applications/{app_id}/credentials?env={env}"
140
- if is_cpd_env(self.base_url)
141
- else f"/connections/applications/{app_id}/credentials/{env}"
142
- )
143
- return self._get(path)
128
+ return self._get(f"/connections/applications/{app_id}/credentials/{env}")
144
129
  else:
145
- path = (
146
- f"/connections/applications/{app_id}/configs/runtime_credentials?env={env}"
147
- if is_cpd_env(self.base_url)
148
- else f"/connections/applications/runtime_credentials?app_id={app_id}&env={env}"
149
- )
150
- return self._get(path)
130
+ return self._get(f"/connections/applications/runtime_credentials?app_id={app_id}&env={env}")
151
131
  except ClientAPIException as e:
152
132
  if e.response.status_code == 404:
153
133
  return None
@@ -177,12 +157,7 @@ class ConnectionsClient(BaseAPIClient):
177
157
  if conn_id is None:
178
158
  return ""
179
159
  try:
180
- path = (
181
- f"/connections/applications/id/{conn_id}"
182
- if is_cpd_env(self.base_url)
183
- else f"/connections/applications?connection_id={conn_id}"
184
- )
185
- app_details = self._get(path)
160
+ app_details = self._get(f"/connections/applications?connection_id={conn_id}")
186
161
  return app_details.get("app_id")
187
162
  except ClientAPIException as e:
188
163
  if e.response.status_code == 404:
@@ -0,0 +1,67 @@
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, agents: Dict[str, Any] = None) -> dict:
25
+ payload = {
26
+ "message": user_message,
27
+ "tools": tools,
28
+ "agents": agents,
29
+ "chat_id": self.chat_id,
30
+ "chat_model_name": self.chat_model_name
31
+ }
32
+
33
+ response = self._post_nd_json("/wxo-cpe/create-agent", data=payload)
34
+
35
+ if response:
36
+ return response[-1]
37
+
38
+
39
+ def init_with_context(self, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
40
+ payload = {
41
+ "context_data": context_data,
42
+ "chat_id": self.chat_id
43
+ }
44
+
45
+ if model:
46
+ payload["target_model_name"] = model
47
+
48
+ response = self._post_nd_json("/wxo-cpe/init_cpe_from_wxo", data=payload)
49
+
50
+ if response:
51
+ return response[-1]
52
+
53
+
54
+ def invoke(self, prompt: str, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
55
+ payload = {
56
+ "prompt": prompt,
57
+ "context_data": context_data,
58
+ "chat_id": self.chat_id
59
+ }
60
+
61
+ if model:
62
+ payload["target_model_name"] = model
63
+
64
+ response = self._post_nd_json("/wxo-cpe/invoke", data=payload)
65
+
66
+ if response:
67
+ 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"