ibm-watsonx-orchestrate 1.12.0b1__py3-none-any.whl → 1.13.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 (59) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
  3. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +11 -2
  4. ibm_watsonx_orchestrate/agent_builder/models/types.py +17 -1
  5. ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
  6. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
  7. ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +61 -1
  8. ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
  9. ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
  10. ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
  11. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +27 -51
  12. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
  13. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +54 -28
  14. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +25 -2
  15. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +249 -14
  16. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
  17. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +5 -1
  18. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -3
  19. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +3 -2
  20. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
  21. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +45 -16
  22. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
  23. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
  24. ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +21 -4
  25. ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +7 -15
  26. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +19 -17
  27. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
  28. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
  29. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +79 -36
  30. ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
  31. ibm_watsonx_orchestrate/cli/common.py +26 -0
  32. ibm_watsonx_orchestrate/cli/config.py +33 -2
  33. ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
  34. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +34 -1
  35. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
  36. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
  37. ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
  38. ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
  39. ibm_watsonx_orchestrate/client/utils.py +29 -7
  40. ibm_watsonx_orchestrate/docker/compose-lite.yml +2 -2
  41. ibm_watsonx_orchestrate/docker/default.env +15 -9
  42. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +2 -0
  43. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +59 -9
  44. ibm_watsonx_orchestrate/flow_builder/node.py +13 -1
  45. ibm_watsonx_orchestrate/flow_builder/types.py +39 -0
  46. ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
  47. ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
  48. ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
  49. ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
  50. ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
  51. ibm_watsonx_orchestrate/utils/environment.py +165 -20
  52. ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
  53. ibm_watsonx_orchestrate/utils/tokens.py +51 -0
  54. ibm_watsonx_orchestrate/utils/utils.py +63 -4
  55. {ibm_watsonx_orchestrate-1.12.0b1.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/METADATA +2 -2
  56. {ibm_watsonx_orchestrate-1.12.0b1.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/RECORD +59 -52
  57. {ibm_watsonx_orchestrate-1.12.0b1.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/WHEEL +0 -0
  58. {ibm_watsonx_orchestrate-1.12.0b1.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/entry_points.txt +0 -0
  59. {ibm_watsonx_orchestrate-1.12.0b1.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
1
  import logging
2
- import asyncio
3
2
  import importlib
4
3
  import inspect
5
4
  import sys
@@ -11,29 +10,28 @@ import zipfile
11
10
  from enum import Enum
12
11
  from os import path
13
12
  from pathlib import Path
14
- from typing import Iterable, List
13
+ from typing import Iterable, List, Any, Optional, cast
15
14
  import rich
16
15
  import json
17
- from rich.json import JSON
18
16
  import glob
19
17
 
20
18
  import rich.table
21
19
  import typer
22
20
 
23
- from rich.console import Console
24
21
  from rich.panel import Panel
25
22
 
26
- from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool, ToolSpec
23
+ from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool, ToolSpec, ToolListEntry
27
24
  from ibm_watsonx_orchestrate.agent_builder.tools.flow_tool import create_flow_json_tool
28
- from ibm_watsonx_orchestrate.agent_builder.tools.langflow_tool import create_langflow_tool
25
+ from ibm_watsonx_orchestrate.agent_builder.tools.langflow_tool import LangflowTool, create_langflow_tool
29
26
  from ibm_watsonx_orchestrate.agent_builder.tools.openapi_tool import create_openapi_json_tools_from_uri,create_openapi_json_tools_from_content
30
27
  from ibm_watsonx_orchestrate.cli.commands.models.models_controller import ModelHighlighter
31
28
  from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
32
29
  from ibm_watsonx_orchestrate.cli.commands.connections.connections_controller import configure_connection, remove_connection, add_connection
30
+ from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
33
31
  from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionType, ConnectionEnvironment, ConnectionPreference
34
32
  from ibm_watsonx_orchestrate.cli.config import Config, CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, \
35
33
  PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, \
36
- DEFAULT_CONFIG_FILE_CONTENT
34
+ DEFAULT_CONFIG_FILE_CONTENT, PYTHON_REGISTRY_SKIP_VERSION_CHECK_OPT
37
35
  from ibm_watsonx_orchestrate.agent_builder.connections import ConnectionSecurityScheme, ExpectedCredentials
38
36
  from ibm_watsonx_orchestrate.flow_builder.flows.decorators import FlowWrapper
39
37
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
@@ -42,6 +40,7 @@ from ibm_watsonx_orchestrate.client.connections import get_connections_client, g
42
40
  from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
43
41
  from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_support_tools
44
42
  from ibm_watsonx_orchestrate.utils.utils import sanitize_app_id
43
+ from ibm_watsonx_orchestrate.utils.async_helpers import run_coroutine_sync
45
44
  from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
46
45
  from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
47
46
 
@@ -56,6 +55,11 @@ DEFAULT_LANGFLOW_TOOL_REQUIREMENTS = [
56
55
  "lfx==0.1.8"
57
56
  ]
58
57
 
58
+ DEFAULT_LANGFLOW_RUNNER_MODULES = [
59
+ "lfx",
60
+ "lfx-nightly"
61
+ ]
62
+
59
63
  class ToolKind(str, Enum):
60
64
  openapi = "openapi"
61
65
  python = "python"
@@ -615,7 +619,7 @@ def get_whl_in_registry(registry_url: str, version: str) -> str| None:
615
619
  return wheel_file
616
620
 
617
621
  class ToolsController:
618
- def __init__(self, tool_kind: ToolKind = None, file: str = None, requirements_file: str = None):
622
+ def __init__(self, tool_kind: ToolKind = None, file: str = None, requirements_file: Optional[str] = None):
619
623
  self.client = None
620
624
  self.tool_kind = tool_kind
621
625
  self.file = file
@@ -651,14 +655,14 @@ class ToolsController:
651
655
  app_id = app_id[0]
652
656
  connection = connections_client.get_draft_by_app_id(app_id=app_id)
653
657
  connection_id = connection.connection_id
654
- tools = asyncio.run(import_openapi_tool(file=args["file"], connection_id=connection_id))
658
+ tools = run_coroutine_sync(import_openapi_tool(file=args["file"], connection_id=connection_id))
655
659
  case "flow":
656
- tools = asyncio.run(import_flow_tool(file=args["file"]))
660
+ tools = run_coroutine_sync(import_flow_tool(file=args["file"]))
657
661
  case "skill":
658
662
  tools = []
659
663
  logger.warning("Skill Import not implemented yet")
660
664
  case "langflow":
661
- tools = asyncio.run(import_langflow_tool(file=args["file"],app_id=args.get('app_id',None)))
665
+ tools = run_coroutine_sync(import_langflow_tool(file=args["file"],app_id=args.get('app_id',None)))
662
666
  case _:
663
667
  raise BadRequest("Invalid kind selected")
664
668
 
@@ -669,14 +673,18 @@ class ToolsController:
669
673
  yield tool
670
674
 
671
675
 
672
- def list_tools(self, verbose=False):
676
+ def list_tools(self, verbose=False, format: ListFormats| None = None) -> List[dict[str, Any]] | str | None:
677
+ if verbose and format:
678
+ logger.error("For tools list, `--verbose` and `--format` are mutually exclusive options")
679
+ sys.exit(1)
680
+
673
681
  response = self.get_client().get()
674
682
  tool_specs = []
675
683
  parse_errors = []
676
684
 
677
685
  for tool in response:
678
686
  try:
679
- tool_specs.append(ToolSpec.model_validate(tool))
687
+ tool_specs.append(ToolSpec.model_validate(tool, context="list"))
680
688
  except Exception as e:
681
689
  name = tool.get('name', None)
682
690
  parse_errors.append([
@@ -693,12 +701,19 @@ class ToolsController:
693
701
  tools_list.append(json.loads(tool.dumps_spec()))
694
702
 
695
703
  rich.print_json(json.dumps(tools_list, indent=4))
704
+ return tools_list
696
705
  else:
706
+ tool_details = []
707
+
708
+ connections_client = get_connections_client()
709
+ connections = connections_client.list()
710
+
711
+ connections_dict = {conn.connection_id: conn for conn in connections}
712
+
697
713
  table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
698
714
  column_args = {
699
715
  "Name": {"overflow": "fold"},
700
716
  "Description": {},
701
- "Permission": {},
702
717
  "Type": {},
703
718
  "Toolkit": {},
704
719
  "App ID": {"overflow": "fold"}
@@ -706,11 +721,6 @@ class ToolsController:
706
721
  for column in column_args:
707
722
  table.add_column(column,**column_args[column])
708
723
 
709
- connections_client = get_connections_client()
710
- connections = connections_client.list()
711
-
712
- connections_dict = {conn.connection_id: conn for conn in connections}
713
-
714
724
  for tool in tools:
715
725
  tool_binding = tool.__tool_spec__.binding
716
726
 
@@ -768,22 +778,31 @@ class ToolsController:
768
778
  toolkit_name = toolkit["name"]
769
779
  elif toolkit:
770
780
  toolkit_name = str(toolkit)
771
-
772
781
 
773
- table.add_row(
774
- tool.__tool_spec__.name,
775
- tool.__tool_spec__.description,
776
- tool.__tool_spec__.permission,
777
- tool_type,
778
- toolkit_name,
779
- ", ".join(app_ids),
782
+ entry = ToolListEntry(
783
+ name=tool.__tool_spec__.name,
784
+ description=tool.__tool_spec__.description,
785
+ type=tool_type,
786
+ toolkit=toolkit_name,
787
+ app_ids=app_ids
780
788
  )
781
789
 
782
- rich.print(table)
790
+ if format == ListFormats.JSON:
791
+ tool_details.append(entry)
792
+ else:
793
+ table.add_row(*entry.get_row_details())
794
+
795
+ match format:
796
+ case ListFormats.JSON:
797
+ return tool_details
798
+ case ListFormats.Table:
799
+ return rich_table_to_markdown(table)
800
+ case _:
801
+ rich.print(table)
783
802
 
784
- for error in parse_errors:
785
- for l in error:
786
- logger.error(l)
803
+ for error in parse_errors:
804
+ for l in error:
805
+ logger.error(l)
787
806
 
788
807
  def get_all_tools(self) -> dict:
789
808
  return {entry["name"]: entry["id"] for entry in self.get_client().get()}
@@ -851,15 +870,18 @@ class ToolsController:
851
870
 
852
871
  cfg = Config()
853
872
  registry_type = cfg.read(PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT) or DEFAULT_CONFIG_FILE_CONTENT[PYTHON_REGISTRY_HEADER][PYTHON_REGISTRY_TYPE_OPT]
873
+ skip_version_check = cfg.read(PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_SKIP_VERSION_CHECK_OPT) or DEFAULT_CONFIG_FILE_CONTENT[PYTHON_REGISTRY_HEADER][PYTHON_REGISTRY_SKIP_VERSION_CHECK_OPT]
854
874
 
855
875
  version = __version__
856
876
  if registry_type == RegistryType.LOCAL:
877
+ logger.warning(f"Using a local registry which is for development purposes only")
857
878
  requirements.append(f"/packages/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl\n")
858
879
  elif registry_type == RegistryType.PYPI:
859
- wheel_file = get_whl_in_registry(registry_url='https://pypi.org/simple/ibm-watsonx-orchestrate', version=version)
860
- if not wheel_file:
861
- logger.error(f"Could not find ibm-watsonx-orchestrate@{version} on https://pypi.org/project/ibm-watsonx-orchestrate")
862
- exit(1)
880
+ if not skip_version_check:
881
+ wheel_file = get_whl_in_registry(registry_url='https://pypi.org/simple/ibm-watsonx-orchestrate', version=version)
882
+ if not wheel_file:
883
+ logger.error(f"Could not find ibm-watsonx-orchestrate@{version} on https://pypi.org/project/ibm-watsonx-orchestrate")
884
+ exit(1)
863
885
  requirements.append(f"ibm-watsonx-orchestrate=={version}\n")
864
886
  elif registry_type == RegistryType.TESTPYPI:
865
887
  override_version = cfg.get(PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT) or version
@@ -890,13 +912,34 @@ class ToolsController:
890
912
  tool_path = Path(self.file)
891
913
  zip_tool_artifacts.write(tool_path, arcname=f"{tool_path.stem}.json")
892
914
 
893
- requirements = DEFAULT_LANGFLOW_TOOL_REQUIREMENTS
915
+ requirements = []
894
916
 
895
917
  if self.requirements_file:
896
918
  requirements_file_path = Path(self.requirements_file)
897
919
  requirements.extend(
898
920
  get_requirement_lines(requirements_file=requirements_file_path, remove_trailing_newlines=False)
899
921
  )
922
+
923
+ langflowTool = cast(LangflowTool, tool)
924
+ # if there are additional requriements from the langflow model, we should add it to the requirement set
925
+ if langflowTool.requirements and len(langflowTool.requirements) > 0:
926
+ requirements.extend(langflowTool.requirements)
927
+
928
+ # now check if the requirements contain modules listed in DEFAULT_LANGFLOW_RUNNER_MODULES
929
+ # if it is needed, we are assuming the user wants to override the default langflow module
930
+ # with a specific version
931
+ runner_overridden = False
932
+ for r in requirements:
933
+ # get the module name from the requirements
934
+ module_name = r.strip().split('==')[0].split('=')[0].split('>=')[0].split('<=')[0].split('~=')[0].lower()
935
+ if not module_name.startswith('#'):
936
+ if module_name in DEFAULT_LANGFLOW_RUNNER_MODULES:
937
+ runner_overridden = True
938
+
939
+ if not runner_overridden:
940
+ # add the default runner to the top of requirement list
941
+ requirements = DEFAULT_LANGFLOW_TOOL_REQUIREMENTS + list(requirements)
942
+
900
943
  requirements_content = '\n'.join(requirements) + '\n'
901
944
  zip_tool_artifacts.writestr("requirements.txt",requirements_content)
902
945
  zip_tool_artifacts.writestr("bundle-format", "2.0.0\n")
@@ -3,10 +3,12 @@ import sys
3
3
  import rich
4
4
  import yaml
5
5
  import logging
6
- from ibm_watsonx_orchestrate.agent_builder.voice_configurations import VoiceConfiguration
6
+ from typing import Optional, List, Any
7
+ from ibm_watsonx_orchestrate.agent_builder.voice_configurations import VoiceConfiguration, VoiceConfigurationListEntry
7
8
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
8
9
  from ibm_watsonx_orchestrate.client.voice_configurations.voice_configurations_client import VoiceConfigurationsClient
9
10
  from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
11
+ from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
10
12
 
11
13
  logger = logging.getLogger(__name__)
12
14
 
@@ -69,12 +71,13 @@ class VoiceConfigurationsController:
69
71
 
70
72
  return configs[0]
71
73
 
72
- def list_voice_configs(self, verbose: bool) -> None:
74
+ def list_voice_configs(self, verbose: bool, format: Optional[ListFormats]=None) -> List[dict[str | Any]] | List[VoiceConfigurationListEntry] | str | None:
73
75
  voice_configs = self.fetch_voice_configs()
74
76
 
75
77
  if verbose:
76
78
  json_configs = [json.loads(x.dumps_spec()) for x in voice_configs]
77
79
  rich.print_json(json.dumps(json_configs, indent=4))
80
+ return json_configs
78
81
  else:
79
82
  config_table = rich.table.Table(
80
83
  show_header=True,
@@ -94,18 +97,27 @@ class VoiceConfigurationsController:
94
97
  for column in column_args:
95
98
  config_table.add_column(column, **column_args[column])
96
99
 
100
+ config_details = []
101
+
97
102
  for config in voice_configs:
98
- attached_agents = [x.display_name or x.name or x.id for x in config.attached_agents]
99
- config_table.add_row(
100
- config.name,
101
- config.voice_configuration_id,
102
- config.speech_to_text.provider,
103
- config.text_to_speech.provider,
104
- ",".join(attached_agents)
103
+ attached_agents = [x.name or x.id for x in config.attached_agents]
104
+ entry = VoiceConfigurationListEntry(
105
+ name=config.name,
106
+ id=config.voice_configuration_id,
107
+ speech_to_text_provider=config.speech_to_text.provider,
108
+ text_to_speech_provider=config.text_to_speech.provider,
109
+ attached_agents=attached_agents
105
110
  )
111
+ config_details.append(entry)
112
+ config_table.add_row(*entry.get_row_details())
106
113
 
107
- rich.print(config_table)
108
-
114
+ match format:
115
+ case ListFormats.JSON:
116
+ return config_details
117
+ case ListFormats.Table:
118
+ return rich_table_to_markdown(config_table)
119
+ case _:
120
+ rich.print(config_table)
109
121
 
110
122
  def create_voice_config(self, voice_config: VoiceConfiguration) -> str | None:
111
123
  client = self.get_voice_configurations_client()
@@ -0,0 +1,26 @@
1
+ from enum import Enum
2
+ from rich.table import Table
3
+
4
+ class ListFormats(str, Enum):
5
+ Table = "table"
6
+ JSON = "json"
7
+
8
+ def __str__(self):
9
+ return self.value
10
+
11
+ def __repr__(self):
12
+ return repr(self.value)
13
+
14
+ def rich_table_to_markdown(table: Table) -> str:
15
+ headers = [column.header for column in table.columns]
16
+ cols = [[cell for cell in col.cells] for col in table.columns]
17
+ rows = list(map(list, zip(*cols)))
18
+
19
+ # Header row
20
+ md = "| " + " | ".join(headers) + " |\n"
21
+ # Separator row
22
+ md += "| " + " | ".join(["---"] * len(headers)) + " |\n"
23
+ # # Data rows
24
+ for row in rows:
25
+ md += "| " + " | ".join(row) + " |\n"
26
+ return md
@@ -22,6 +22,7 @@ AUTH_MCSP_TOKEN_OPT = "wxo_mcsp_token"
22
22
  AUTH_MCSP_TOKEN_EXPIRY_OPT = "wxo_mcsp_token_expiry"
23
23
  CONTEXT_ACTIVE_ENV_OPT = "active_environment"
24
24
  PYTHON_REGISTRY_TYPE_OPT = "type"
25
+ PYTHON_REGISTRY_SKIP_VERSION_CHECK_OPT = "skip_version_check"
25
26
  PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT = "test_package_version_override"
26
27
  ENV_WXO_URL_OPT = "wxo_url"
27
28
  ENV_IAM_URL_OPT = "iam_url"
@@ -40,7 +41,8 @@ DEFAULT_CONFIG_FILE_CONTENT = {
40
41
  CONTEXT_SECTION_HEADER: {CONTEXT_ACTIVE_ENV_OPT: None},
41
42
  PYTHON_REGISTRY_HEADER: {
42
43
  PYTHON_REGISTRY_TYPE_OPT: str(RegistryType.PYPI),
43
- PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT: None
44
+ PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT: None,
45
+ PYTHON_REGISTRY_SKIP_VERSION_CHECK_OPT: False
44
46
  },
45
47
  ENVIRONMENTS_SECTION_HEADER: {
46
48
  PROTECTED_ENV_NAME: {
@@ -85,7 +87,8 @@ def _check_if_auth_config_file(folder, file):
85
87
 
86
88
  def clear_protected_env_credentials_token():
87
89
  auth_cfg = Config(config_file_folder=AUTH_CONFIG_FILE_FOLDER, config_file=AUTH_CONFIG_FILE)
88
- auth_cfg.delete(AUTH_SECTION_HEADER, PROTECTED_ENV_NAME, AUTH_MCSP_TOKEN_OPT)
90
+ if auth_cfg.exists(AUTH_SECTION_HEADER, PROTECTED_ENV_NAME, AUTH_MCSP_TOKEN_OPT):
91
+ auth_cfg.delete(AUTH_SECTION_HEADER, PROTECTED_ENV_NAME, AUTH_MCSP_TOKEN_OPT)
89
92
 
90
93
 
91
94
  class ConfigFileTypes(str, Enum):
@@ -230,3 +233,31 @@ class Config:
230
233
 
231
234
  with open(self.config_file_path, 'w') as conf_file:
232
235
  yaml.dump(deletion_data, conf_file, allow_unicode=True)
236
+
237
+ def exists(self, *args) -> bool:
238
+ """
239
+ Determines if an item of arbitrary depth exists in the config file.
240
+ Takes an arbitrary number of args. Uses the args in order
241
+ as keys to access deeper sections of the config and then deleting the last specified key.
242
+ """
243
+ if len(args) < 1:
244
+ raise BadRequest("Config.delete() requires at least one positional argument")
245
+
246
+ config_data = {}
247
+ try:
248
+ with open(self.config_file_path, 'r') as conf_file:
249
+ config_data = yaml_safe_load(conf_file) or {}
250
+ except FileNotFoundError:
251
+ pass
252
+
253
+ expression = "config_data"
254
+ for key in args:
255
+ temp = eval(expression)
256
+
257
+ if not isinstance(temp, dict) or key not in temp:
258
+ return False
259
+
260
+ else:
261
+ expression += f"['{key}']"
262
+
263
+ return True
@@ -3,27 +3,14 @@ from typing import List
3
3
  from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
4
4
  from pydantic import BaseModel, ValidationError
5
5
  from typing import Optional
6
- from enum import Enum
7
6
 
8
7
  from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
9
- from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionConfiguration, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
8
+ from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionConfiguration, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType, FetchConfigAuthTypes
10
9
  from ibm_watsonx_orchestrate.client.utils import is_cpd_env, is_local_dev
11
10
 
12
11
  import logging
13
12
  logger = logging.getLogger(__name__)
14
13
 
15
-
16
- class FetchConfigAuthTypes(str, Enum):
17
- BASIC_AUTH = ConnectionType.BASIC_AUTH.value
18
- BEARER_TOKEN = ConnectionType.BEARER_TOKEN.value
19
- API_KEY_AUTH = ConnectionType.API_KEY_AUTH.value
20
- OAUTH2_AUTH_CODE = ConnectionType.OAUTH2_AUTH_CODE.value
21
- OAUTH2_IMPLICIT = 'oauth2_implicit'
22
- OAUTH2_PASSWORD = ConnectionType.OAUTH2_PASSWORD.value
23
- OAUTH2_CLIENT_CREDS = ConnectionType.OAUTH2_CLIENT_CREDS.value
24
- OAUTH_ON_BEHALF_OF_FLOW = ConnectionType.OAUTH_ON_BEHALF_OF_FLOW.value
25
- KEY_VALUE = ConnectionType.KEY_VALUE.value
26
-
27
14
  class ListConfigsResponse(BaseModel):
28
15
  connection_id: str = None,
29
16
  app_id: str = None
@@ -1,4 +1,4 @@
1
- from typing import Dict, Any
1
+ from typing import Dict, Any, List
2
2
  from uuid import uuid4
3
3
 
4
4
  from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
@@ -37,6 +37,39 @@ class CPEClient(BaseAPIClient):
37
37
  if response:
38
38
  return response[-1]
39
39
 
40
+ def refine_agent_with_chats(self, instruction: str, tools: Dict[str, Any], collaborators: Dict[str, Any], knowledge_bases: Dict[str, Any], trajectories_with_feedback: List[List[dict]], model: str | None = None) -> dict:
41
+ """
42
+ Refines an agent's instruction using provided chat trajectories and optional model name.
43
+ This method sends a payload containing the agent's current instruction and a list of chat trajectories
44
+ to the Copilot Prompt Engine (CPE) for refinement.
45
+ Optionally, a target model name can be specified to use in the refinement process.
46
+ Parameters:
47
+ instruction (str): The current instruction or prompt associated with the agent.
48
+ tools (Dict[str, Any]) - a dictionary containing the selected tools
49
+ collaborators (Dict[str, Any]) - a dictionary containing the selected collaborators
50
+ knowledge_bases (Dict[str, Any]) - a dictionary containing the selected knowledge_bases
51
+ trajectories_with_feedback (List[List[dict]]): A list of chat trajectories, where each trajectory is a list
52
+ of message dictionaries that may include user feedback.
53
+ model (str | None): Optional. The name of the model to use for refinement.
54
+ Returns:
55
+ dict: The last response from the CPE containing the refined instruction.
56
+ """
57
+
58
+ payload = {
59
+ "trajectories_with_feedback":trajectories_with_feedback,
60
+ "instruction":instruction,
61
+ "tools": tools,
62
+ "collaborators": collaborators,
63
+ "knowledge_bases": knowledge_bases
64
+ }
65
+
66
+ if model:
67
+ payload["target_model_name"] = model
68
+
69
+ response = self._post_nd_json("/wxo-cpe/refine-agent-with-trajectories", data=payload)
70
+
71
+ if response:
72
+ return response[-1]
40
73
 
41
74
  def init_with_context(self, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
42
75
  payload = {
@@ -30,8 +30,12 @@ class KnowledgeBaseClient(BaseAPIClient):
30
30
  def get_by_id(self, knowledge_base_id: str) -> dict:
31
31
  return self._get(f"{self.base_endpoint}/{knowledge_base_id}")
32
32
 
33
- def get_by_names(self, name: List[str]) -> List[dict]:
34
- formatted_names = [f"names={x}" for x in name]
33
+ def get_by_names(self, names: List[str]) -> List[dict]:
34
+ formatted_names = [f"names={x}" for x in names]
35
+ return self._get(f"{self.base_endpoint}?{'&'.join(formatted_names)}")
36
+
37
+ def get_by_ids(self, ids: List[str]) -> List[dict]:
38
+ formatted_names = [f"ids={x}" for x in ids]
35
39
  return self._get(f"{self.base_endpoint}?{'&'.join(formatted_names)}")
36
40
 
37
41
  def status(self, knowledge_base_id: str) -> dict:
@@ -58,7 +58,7 @@ class ModelPoliciesClient(BaseAPIClient):
58
58
  return []
59
59
  raise e
60
60
 
61
- def get_draft_by_name(self, policy_name: str) -> ModelPolicy:
61
+ def get_draft_by_name(self, policy_name: str) -> List[ModelPolicy]:
62
62
  return self.get_drafts_by_names([policy_name])
63
63
 
64
64
 
@@ -60,7 +60,7 @@ class ModelsClient(BaseAPIClient):
60
60
  return []
61
61
  raise e
62
62
 
63
- def get_draft_by_name(self, model_name: str) -> ListVirtualModel:
63
+ def get_draft_by_name(self, model_name: str) -> List[ListVirtualModel]:
64
64
  return self.get_drafts_by_names([model_name])
65
65
 
66
66
 
@@ -0,0 +1,34 @@
1
+ from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
2
+
3
+
4
+ class ThreadsClient(BaseAPIClient):
5
+ """
6
+ Client to handle read operations for Threads (chat history- trajectories) endpoints
7
+ """
8
+
9
+ def __init__(self, *args, **kwargs):
10
+ super().__init__(*args, **kwargs)
11
+ self.base_endpoint = "/threads"
12
+
13
+ def get_all_threads(self, agent_id) -> dict:
14
+ return self._get(self.base_endpoint, params={"agent_id": agent_id})
15
+
16
+ def get_thread_messages(self, thread_id) -> dict:
17
+ return self._get(f"{self.base_endpoint}/{thread_id}/messages")
18
+
19
+ def get(self) -> dict:
20
+ return self._get(self.base_endpoint)
21
+
22
+ def get_threads_messages(self, thread_ids: list[str]):
23
+ """
24
+ get the messages for a list of threads (chats) ids
25
+ :param thread_ids:
26
+ :param threads_client:
27
+ :return:
28
+ """
29
+ all_thread_messages = []
30
+ for thread_id in thread_ids:
31
+ thread_messages = self.get_thread_messages(thread_id=thread_id)
32
+ all_thread_messages.append(thread_messages)
33
+
34
+ return all_thread_messages
@@ -175,23 +175,24 @@ def instantiate_client(client: type[T] , url: str | None=None) -> T:
175
175
  raise FileNotFoundError(message)
176
176
 
177
177
 
178
+ def get_arm_architectures () -> list[str]:
179
+ # NOTE: intentionally omitting 32 bit arm architectures.
180
+ return ["aarch64", "arm64", "arm", "aarch64_be", "armv8b", "armv8l"]
181
+
182
+
178
183
  def get_architecture () -> str:
179
184
  arch = platform.machine().lower()
180
185
  if arch in ("amd64", "x86_64"):
181
186
  return "amd64"
182
-
183
- elif arch == "i386":
184
- return "386"
185
-
186
- elif arch in ("aarch64", "arm64", "arm"):
187
- return "arm"
187
+ elif arch in get_arm_architectures():
188
+ return arch
188
189
 
189
190
  else:
190
191
  raise Exception("Unsupported architecture %s" % arch)
191
192
 
192
193
 
193
194
  def is_arm_architecture () -> bool:
194
- return platform.machine().lower() in ("aarch64", "arm64", "arm")
195
+ return platform.machine().lower() in get_arm_architectures()
195
196
 
196
197
 
197
198
  def get_os_type () -> str:
@@ -201,3 +202,24 @@ def get_os_type () -> str:
201
202
 
202
203
  else:
203
204
  raise Exception("Unsupported operating system %s" % system)
205
+
206
+ def concat_bin_files(target_bin_file: str, source_files: list[str], read_chunk_size: int = None,
207
+ delete_source_files_post: bool = True) -> None:
208
+ if read_chunk_size is None:
209
+ # default read chunk size is 100 MB.
210
+ read_chunk_size = 100 * 1024 * 1024
211
+
212
+ with open(target_bin_file, "wb") as target:
213
+ for source_file in source_files:
214
+ with open(source_file, "rb") as source:
215
+ while True:
216
+ source_chunk = source.read(read_chunk_size)
217
+
218
+ if source_chunk:
219
+ target.write(source_chunk)
220
+
221
+ else:
222
+ break
223
+
224
+ if delete_source_files_post is True:
225
+ os.remove(source_file)
@@ -241,7 +241,7 @@ services:
241
241
  - "wxo-server-minio"
242
242
 
243
243
  wxo-milvus-etcd:
244
- image: quay.io/coreos/etcd:v3.5.18
244
+ image: ${ETCD_REGISTRY:-quay.io}/coreos/etcd:${ETCD_TAG:-v3.5.18}
245
245
  environment:
246
246
  - ETCD_AUTO_COMPACTION_MODE=revision
247
247
  - ETCD_AUTO_COMPACTION_RETENTION=1000
@@ -580,7 +580,7 @@ services:
580
580
  # IBM AGENT-OPS
581
581
  ########################
582
582
  elasticsearch:
583
- image: docker.elastic.co/elasticsearch/elasticsearch:8.19.0
583
+ image: ${ELASTICSEARCH_REGISTRY:-docker.elastic.co}/elasticsearch/elasticsearch:${ELASTICSEARCH_TAG:-8.19.0}
584
584
  profiles: [ibm-telemetry]
585
585
  environment:
586
586
  - discovery.type=single-node