ibm-watsonx-orchestrate 1.6.2__py3-none-any.whl → 1.6.4__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 (60) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -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/connections/connections.py +4 -3
  7. ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -2
  8. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
  9. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
  10. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
  11. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +75 -24
  12. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
  13. ibm_watsonx_orchestrate/agent_builder/tools/types.py +17 -11
  14. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +7 -6
  16. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
  17. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +35 -25
  18. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  19. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -8
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  21. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
  22. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +5 -5
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +3 -1
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +102 -37
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +20 -2
  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 +36 -20
  29. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  30. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
  31. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +94 -36
  32. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  33. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +11 -4
  34. ibm_watsonx_orchestrate/cli/config.py +3 -3
  35. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  36. ibm_watsonx_orchestrate/cli/main.py +5 -0
  37. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  38. ibm_watsonx_orchestrate/client/connections/connections_client.py +5 -30
  39. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
  40. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  41. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  42. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  43. ibm_watsonx_orchestrate/client/utils.py +49 -8
  44. ibm_watsonx_orchestrate/docker/compose-lite.yml +198 -6
  45. ibm_watsonx_orchestrate/docker/default.env +36 -12
  46. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +9 -4
  47. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  48. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  49. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +131 -20
  50. ibm_watsonx_orchestrate/flow_builder/node.py +18 -1
  51. ibm_watsonx_orchestrate/flow_builder/types.py +271 -16
  52. ibm_watsonx_orchestrate/flow_builder/utils.py +120 -6
  53. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  54. {ibm_watsonx_orchestrate-1.6.2.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/METADATA +3 -7
  55. {ibm_watsonx_orchestrate-1.6.2.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/RECORD +58 -55
  56. ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +0 -149
  57. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  58. {ibm_watsonx_orchestrate-1.6.2.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/WHEEL +0 -0
  59. {ibm_watsonx_orchestrate-1.6.2.dist-info → ibm_watsonx_orchestrate-1.6.4.dist-info}/entry_points.txt +0 -0
  60. {ibm_watsonx_orchestrate-1.6.2.dist-info → ibm_watsonx_orchestrate-1.6.4.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,10 +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
-
317
- def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=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:
318
340
  compose_path = get_compose_file()
319
341
  compose_command = ensure_docker_compose_installed()
342
+ _prepare_clean_env(final_env_file)
320
343
  db_tag = read_env_file(final_env_file).get('DBTAG', None)
321
344
  logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
322
345
 
@@ -341,19 +364,17 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
341
364
 
342
365
 
343
366
  # Step 2: Start all remaining services (except DB)
367
+ profiles = []
344
368
  if experimental_with_langfuse:
345
- command = compose_command + [
346
- '--profile',
347
- 'langfuse'
348
- ]
349
- elif experimental_with_ibm_telemetry:
350
- command = compose_command + [
351
- '--profile',
352
- 'ibm-telemetry'
353
- ]
354
- else:
355
- command = compose_command
369
+ profiles.append("langfuse")
370
+ if experimental_with_ibm_telemetry:
371
+ profiles.append("ibm-telemetry")
372
+ if with_doc_processing:
373
+ profiles.append("docproc")
356
374
 
375
+ command = compose_command[:]
376
+ for profile in profiles:
377
+ command += ["--profile", profile]
357
378
 
358
379
  command += [
359
380
  "-f", str(compose_path),
@@ -361,6 +382,8 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
361
382
  "up",
362
383
  "--scale",
363
384
  "ui=0",
385
+ "--scale",
386
+ "cpe=0",
364
387
  "-d",
365
388
  "--remove-orphans",
366
389
  ]
@@ -431,6 +454,7 @@ def wait_for_wxo_ui_health_check(timeout_seconds=45, interval_seconds=2):
431
454
  def run_compose_lite_ui(user_env_file: Path) -> bool:
432
455
  compose_path = get_compose_file()
433
456
  compose_command = ensure_docker_compose_installed()
457
+ _prepare_clean_env(user_env_file)
434
458
  ensure_docker_installed()
435
459
 
436
460
  default_env = read_env_file(get_default_env_file())
@@ -525,6 +549,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
525
549
  def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> None:
526
550
  compose_path = get_compose_file()
527
551
  compose_command = ensure_docker_compose_installed()
552
+ _prepare_clean_env(user_env_file)
528
553
 
529
554
 
530
555
  ensure_docker_installed()
@@ -568,6 +593,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
568
593
  def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
569
594
  compose_path = get_compose_file()
570
595
  compose_command = ensure_docker_compose_installed()
596
+ _prepare_clean_env(final_env_file)
571
597
 
572
598
  command = compose_command + [
573
599
  '--profile', '*',
@@ -600,6 +626,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
600
626
  def run_compose_lite_logs(final_env_file: Path, is_reset: bool = False) -> None:
601
627
  compose_path = get_compose_file()
602
628
  compose_command = ensure_docker_compose_installed()
629
+ _prepare_clean_env(final_env_file)
603
630
 
604
631
  command = compose_command + [
605
632
  "-f", str(compose_path),
@@ -655,63 +682,63 @@ def confirm_accepts_license_agreement(accepts_by_argument: bool):
655
682
  def auto_configure_callback_ip(merged_env_dict: dict) -> dict:
656
683
  """
657
684
  Automatically detect and configure CALLBACK_HOST_URL if it's empty.
658
-
685
+
659
686
  Args:
660
687
  merged_env_dict: The merged environment dictionary
661
-
688
+
662
689
  Returns:
663
690
  Updated environment dictionary with CALLBACK_HOST_URL set
664
691
  """
665
692
  callback_url = merged_env_dict.get('CALLBACK_HOST_URL', '').strip()
666
-
693
+
667
694
  # Only auto-configure if CALLBACK_HOST_URL is empty
668
695
  if not callback_url:
669
696
  logger.info("Auto-detecting local IP address for async tool callbacks...")
670
-
697
+
671
698
  system = platform.system()
672
699
  ip = None
673
-
700
+
674
701
  try:
675
702
  if system in ("Linux", "Darwin"):
676
703
  result = subprocess.run(["ifconfig"], capture_output=True, text=True, check=True)
677
704
  lines = result.stdout.splitlines()
678
-
705
+
679
706
  for line in lines:
680
707
  line = line.strip()
681
708
  # Unix ifconfig output format: "inet 192.168.1.100 netmask 0xffffff00 broadcast 192.168.1.255"
682
709
  if line.startswith("inet ") and "127.0.0.1" not in line:
683
710
  candidate_ip = line.split()[1]
684
711
  # Validate IP is not loopback or link-local
685
- if (candidate_ip and
686
- not candidate_ip.startswith("127.") and
712
+ if (candidate_ip and
713
+ not candidate_ip.startswith("127.") and
687
714
  not candidate_ip.startswith("169.254")):
688
715
  ip = candidate_ip
689
716
  break
690
-
717
+
691
718
  elif system == "Windows":
692
719
  result = subprocess.run(["ipconfig"], capture_output=True, text=True, check=True)
693
720
  lines = result.stdout.splitlines()
694
-
721
+
695
722
  for line in lines:
696
723
  line = line.strip()
697
724
  # Windows ipconfig output format: " IPv4 Address. . . . . . . . . . . : 192.168.1.100"
698
725
  if "IPv4 Address" in line and ":" in line:
699
726
  candidate_ip = line.split(":")[-1].strip()
700
727
  # Validate IP is not loopback or link-local
701
- if (candidate_ip and
702
- not candidate_ip.startswith("127.") and
728
+ if (candidate_ip and
729
+ not candidate_ip.startswith("127.") and
703
730
  not candidate_ip.startswith("169.254")):
704
731
  ip = candidate_ip
705
732
  break
706
-
733
+
707
734
  else:
708
735
  logger.warning(f"Unsupported platform: {system}")
709
736
  ip = None
710
-
737
+
711
738
  except Exception as e:
712
739
  logger.debug(f"IP detection failed on {system}: {e}")
713
740
  ip = None
714
-
741
+
715
742
  if ip:
716
743
  callback_url = f"http://{ip}:4321"
717
744
  merged_env_dict['CALLBACK_HOST_URL'] = callback_url
@@ -724,7 +751,7 @@ def auto_configure_callback_ip(merged_env_dict: dict) -> dict:
724
751
  logger.info("For external tools, consider using ngrok or similar tunneling service.")
725
752
  else:
726
753
  logger.info(f"Using existing CALLBACK_HOST_URL: {callback_url}")
727
-
754
+
728
755
  return merged_env_dict
729
756
 
730
757
  @server_app.command(name="start")
@@ -755,9 +782,16 @@ def server_start(
755
782
  "--accept-terms-and-conditions",
756
783
  help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
757
784
  ),
785
+ with_doc_processing: bool = typer.Option(
786
+ False,
787
+ '--with-doc-processing', '-d',
788
+ help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
789
+ ),
758
790
  ):
759
791
  confirm_accepts_license_agreement(accept_terms_and_conditions)
760
792
 
793
+ define_saas_wdu_runtime()
794
+
761
795
  if user_env_file and not Path(user_env_file).exists():
762
796
  logger.error(f"Error: The specified environment file '{user_env_file}' does not exist.")
763
797
  sys.exit(1)
@@ -789,10 +823,14 @@ def server_start(
789
823
  logger.error("Please select either langfuse or ibm telemetry for observability not both")
790
824
  sys.exit(1)
791
825
 
792
- # Add LANGFUSE_ENABLED into the merged_env_dict, for tempus to pick up.
826
+ # Add LANGFUSE_ENABLED and DOCPROC_ENABLED into the merged_env_dict, for tempus to pick up.
793
827
  if experimental_with_langfuse:
794
828
  merged_env_dict['LANGFUSE_ENABLED'] = 'true'
795
-
829
+
830
+ if with_doc_processing:
831
+ merged_env_dict['DOCPROC_ENABLED'] = 'true'
832
+ define_saas_wdu_runtime("local")
833
+
796
834
  if experimental_with_ibm_telemetry:
797
835
  merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
798
836
 
@@ -806,10 +844,12 @@ def server_start(
806
844
 
807
845
 
808
846
  final_env_file = write_merged_env_file(merged_env_dict)
809
- run_compose_lite(final_env_file=final_env_file,
810
- experimental_with_langfuse=experimental_with_langfuse,
811
- experimental_with_ibm_telemetry=experimental_with_ibm_telemetry)
812
847
 
848
+ run_compose_lite(final_env_file=final_env_file,
849
+ experimental_with_langfuse=experimental_with_langfuse,
850
+ experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
851
+ with_doc_processing=with_doc_processing)
852
+
813
853
  run_db_migration()
814
854
 
815
855
  logger.info("Waiting for orchestrate server to be fully initialized and ready...")
@@ -836,6 +876,8 @@ def server_start(
836
876
 
837
877
  if experimental_with_langfuse:
838
878
  logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
879
+ if with_doc_processing:
880
+ logger.info(f"Document processing in Flows (Public Preview) has been enabled.")
839
881
 
840
882
  @server_app.command(name="stop")
841
883
  def server_stop(
@@ -845,6 +887,7 @@ def server_stop(
845
887
  help="Path to a .env file that overrides default.env. Then environment variables override both."
846
888
  )
847
889
  ):
890
+
848
891
  ensure_docker_installed()
849
892
  default_env_path = get_default_env_file()
850
893
  merged_env_dict = merge_env(
@@ -901,9 +944,24 @@ def server_logs(
901
944
  def run_db_migration() -> None:
902
945
  compose_path = get_compose_file()
903
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)
904
961
 
905
962
  command = compose_command + [
906
963
  "-f", str(compose_path),
964
+ "--env-file", str(final_env_file),
907
965
  "exec",
908
966
  "wxo-server-db",
909
967
  "bash",
@@ -943,4 +1001,4 @@ def run_db_migration() -> None:
943
1001
  sys.exit(1)
944
1002
 
945
1003
  if __name__ == "__main__":
946
- server_app()
1004
+ server_app()
@@ -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
 
@@ -400,7 +400,14 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
400
400
 
401
401
  [bold cyan]Additional information:[/bold cyan]
402
402
 
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].
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.
404
411
 
405
412
  """
406
413
 
@@ -477,7 +484,7 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
477
484
  permission="read_only",
478
485
  flow_model=model)
479
486
 
480
- tools = import_flow_support_tools()
487
+ tools = import_flow_support_tools(model=model)
481
488
 
482
489
  tools.append(tool)
483
490
 
@@ -554,7 +561,7 @@ class ToolsController:
554
561
  tools = []
555
562
  logger.warning("Skill Import not implemented yet")
556
563
  case _:
557
- raise ValueError("Invalid kind selected")
564
+ raise BadRequest("Invalid kind selected")
558
565
 
559
566
  for tool in tools:
560
567
  yield tool
@@ -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"