ibm-watsonx-orchestrate 1.11.0b0__py3-none-any.whl → 1.12.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 (47) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +30 -0
  3. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +8 -5
  4. ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -0
  5. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +25 -10
  6. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -0
  7. ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +124 -0
  8. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +3 -3
  9. ibm_watsonx_orchestrate/agent_builder/tools/types.py +20 -2
  10. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +10 -2
  11. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +421 -177
  12. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +18 -0
  13. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +114 -0
  14. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +24 -91
  15. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +1 -1
  16. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +223 -2
  17. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +93 -9
  18. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +3 -3
  19. ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_command.py +56 -0
  20. ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +458 -0
  21. ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +107 -0
  22. ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +12 -0
  23. ibm_watsonx_orchestrate/cli/commands/partners/partners_controller.py +0 -0
  24. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +114 -635
  25. ibm_watsonx_orchestrate/cli/commands/server/types.py +1 -1
  26. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +2 -2
  27. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -3
  28. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +206 -43
  29. ibm_watsonx_orchestrate/cli/main.py +2 -0
  30. ibm_watsonx_orchestrate/client/base_api_client.py +31 -10
  31. ibm_watsonx_orchestrate/client/connections/connections_client.py +18 -1
  32. ibm_watsonx_orchestrate/client/service_instance.py +19 -34
  33. ibm_watsonx_orchestrate/client/tools/tempus_client.py +3 -0
  34. ibm_watsonx_orchestrate/client/tools/tool_client.py +5 -2
  35. ibm_watsonx_orchestrate/client/utils.py +34 -2
  36. ibm_watsonx_orchestrate/docker/compose-lite.yml +14 -12
  37. ibm_watsonx_orchestrate/docker/default.env +17 -17
  38. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +3 -1
  39. ibm_watsonx_orchestrate/flow_builder/types.py +252 -1
  40. ibm_watsonx_orchestrate/utils/docker_utils.py +280 -0
  41. ibm_watsonx_orchestrate/utils/environment.py +369 -0
  42. ibm_watsonx_orchestrate/utils/utils.py +1 -1
  43. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/METADATA +2 -2
  44. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/RECORD +47 -39
  45. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/WHEEL +0 -0
  46. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/entry_points.txt +0 -0
  47. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -12,6 +12,7 @@ from pathlib import Path
12
12
  from copy import deepcopy
13
13
 
14
14
  from typing import Iterable, List, TypeVar
15
+ from pydantic import BaseModel
15
16
  from ibm_watsonx_orchestrate.agent_builder.agents.types import AgentStyle
16
17
  from ibm_watsonx_orchestrate.agent_builder.tools.types import ToolSpec
17
18
  from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import import_python_tool, ToolsController
@@ -40,11 +41,24 @@ from ibm_watsonx_orchestrate.utils.utils import check_file_in_zip
40
41
  from rich.console import Console
41
42
  from rich.progress import Progress, SpinnerColumn, TextColumn
42
43
 
44
+ from enum import Enum
45
+
43
46
  logger = logging.getLogger(__name__)
44
47
 
45
48
  # Helper generic type for any agent
46
49
  AnyAgentT = TypeVar("AnyAgentT", bound=Agent | ExternalAgent | AssistantAgent)
47
50
 
51
+ class AgentListFormats(str, Enum):
52
+ Table = "table"
53
+ JSON = "json"
54
+
55
+ def __str__(self):
56
+ return self.value
57
+
58
+ def __repr__(self):
59
+ return repr(self.value)
60
+
61
+
48
62
  def import_python_agent(file: str) -> List[Agent | ExternalAgent | AssistantAgent]:
49
63
  # Import tools
50
64
  import_python_tool(file)
@@ -100,6 +114,7 @@ def parse_create_native_args(name: str, kind: AgentKind, description: str | None
100
114
  "name": name,
101
115
  "kind": kind,
102
116
  "description": description,
117
+ "instructions": args.get("instructions"),
103
118
  "llm": args.get("llm"),
104
119
  "style": args.get("style"),
105
120
  "custom_join_tool": args.get("custom_join_tool"),
@@ -804,213 +819,429 @@ class AgentsController:
804
819
  logger.warning(f"Knowledge base with ID {id} not found. Returning Tool ID")
805
820
  knowledge_bases.append(id)
806
821
  return knowledge_bases
807
-
808
- def list_agents(self, kind: AgentKind=None, verbose: bool=False):
822
+
823
+ def _fetch_and_parse_agents(self, target_agent_kind: AgentKind) -> tuple[List[Agent] | List[ExternalAgent] | List[AssistantAgent], List[List[str]]]:
809
824
  parse_errors = []
825
+ target_kind_display_name = None
826
+ target_kind_class = None
827
+ agent_client = None
810
828
 
811
- if verbose:
812
- verbose_output_dictionary = {
813
- "native":[],
814
- "assistant":[],
815
- "external":[]
816
- }
829
+ match(target_agent_kind):
830
+ case AgentKind.NATIVE:
831
+ target_kind_display_name = "Agent"
832
+ target_kind_class = Agent
833
+ agent_client = self.get_native_client()
834
+ case AgentKind.EXTERNAL:
835
+ target_kind_display_name = "External Agent"
836
+ target_kind_class = ExternalAgent
837
+ agent_client = self.get_external_client()
838
+ case AgentKind.ASSISTANT:
839
+ target_kind_display_name = "Assistant Agent"
840
+ target_kind_class = AssistantAgent
841
+ agent_client = self.get_assistant_client()
842
+ case _:
843
+ return ([], [[f"Invalid Agent kind '{target_agent_kind}'"]])
844
+
845
+ response = agent_client.get()
846
+ agents = []
847
+ for agent in response:
848
+ try:
849
+ agents.append(target_kind_class.model_validate(agent))
850
+ except Exception as e:
851
+ name = agent.get('name', None)
852
+ parse_errors.append([
853
+ f"{target_kind_display_name} '{name}' could not be parsed",
854
+ json.dumps(agent),
855
+ e
856
+ ])
857
+ return (agents, parse_errors)
858
+
859
+ def _get_all_unique_agent_resources(self, agents: List[Agent], target_attr: str) -> List[str]:
860
+ """
861
+ Given a list if agents get all the unique values of a certain field
862
+ Example: agent1.tools = [1 ,2 ,3] and agent2.tools = [2, 4, 5] then return [1, 2, 3, 4, 5]
863
+ Example: agent1.id = "123" and agent2.id = "456" then return ["123", "456"]
864
+
865
+ Args:
866
+ agents: List of agents
867
+ target_attr: The name of the field to access and get unique elements
868
+
869
+ Returns:
870
+ A list of unique elements from across all agents
871
+ """
872
+ all_ids = set()
873
+ for agent in agents:
874
+ attr_value = getattr(agent, target_attr, None)
875
+ if attr_value:
876
+ if isinstance(attr_value, list):
877
+ all_ids.update(attr_value)
878
+ else:
879
+ all_ids.add(attr_value)
880
+ return list(all_ids)
881
+
882
+ def _construct_lut_agent_resource(self, resource_list: List[dict], key_attr: str, value_attr) -> dict:
883
+ """
884
+ Given a list of dictionaries build a key -> value look up table
885
+ Example [{id: 1, name: obj1}, {id: 2, name: obj2}] return {1: obj1, 2: obj2}
886
+
887
+ Args:
888
+ resource_list: A list of dictionries from which to build the lookup table from
889
+ key_attr: The name of the field whose value will form the key of the lookup table
890
+ value_attrL The name of the field whose value will form the value of the lookup table
891
+
892
+ Returns:
893
+ A lookup table
894
+ """
895
+ lut = {}
896
+ for resource in resource_list:
897
+ if isinstance(resource, BaseModel):
898
+ resource = resource.model_dump()
899
+ lut[resource.get(key_attr, None)] = resource.get(value_attr, None)
900
+ return lut
901
+
902
+ def _lookup_agent_resource_value(
903
+ self,
904
+ agent: Agent,
905
+ lookup_table: dict[str, str],
906
+ target_attr: str,
907
+ target_attr_display_name: str
908
+ ) -> List[str] | str | None:
909
+ """
910
+ Using a lookup table convert all the strings in a given field of an agent into their equivalent in the lookup table
911
+ Example: lookup_table={1: obj1, 2: obj2} agent=Agent(tools=[1,2]) return. [obj1, obj2]
912
+
913
+ Args:
914
+ agent: An agent
915
+ lookup_table: A dictionary that maps one value to another
916
+ target_attr: The field to convert on the provided agent
917
+ target_attr_display_name: The name of the field to be displayed in the event of an error
918
+ """
919
+ attr_value = getattr(agent, target_attr, None)
920
+ if not attr_value:
921
+ return
922
+
923
+ if isinstance(attr_value, list):
924
+ new_resource_list=[]
925
+ for value in attr_value:
926
+ if value in lookup_table:
927
+ new_resource_list.append(lookup_table[value])
928
+ else:
929
+ logger.warning(f"{target_attr_display_name} with ID '{value}' not found. Returning {target_attr_display_name} ID")
930
+ new_resource_list.append(value)
931
+ return new_resource_list
932
+ else:
933
+ if attr_value in lookup_table:
934
+ return lookup_table[attr_value]
935
+ else:
936
+ logger.warning(f"{target_attr_display_name} with ID '{attr_value}' not found. Returning {target_attr_display_name} ID")
937
+ return attr_value
938
+
939
+ def _batch_request_resource(self, client_fn, ids, batch_size=50) -> List[dict]:
940
+ resources = []
941
+ for i in range(0, len(ids), batch_size):
942
+ chunk = ids[i:i + batch_size]
943
+ resources += (client_fn(chunk))
944
+ return resources
945
+
946
+
947
+ def _bulk_resolve_agent_tools(self, agents: List[Agent]) -> List[Agent]:
948
+ new_agents = agents.copy()
949
+ all_tools_ids = self._get_all_unique_agent_resources(new_agents, "tools")
950
+ if not all_tools_ids:
951
+ return new_agents
952
+
953
+ all_tools = self._batch_request_resource(self.get_tool_client().get_drafts_by_ids, all_tools_ids)
954
+
955
+ tool_lut = self._construct_lut_agent_resource(all_tools, "id", "name")
956
+
957
+ for agent in new_agents:
958
+ tool_names = self._lookup_agent_resource_value(agent, tool_lut, "tools", "Tool")
959
+ if tool_names:
960
+ agent.tools = tool_names
961
+ return new_agents
962
+
963
+ # TODO: Once bulk knowledge base is added create a generaic fucntion as opposed to 3 seperate ones
964
+ def _bulk_resolve_agent_knowledge_bases(self, agents: List[Agent]) -> List[Agent]:
965
+ new_agents = agents.copy()
966
+ all_kb_ids = self._get_all_unique_agent_resources(new_agents, "knowledge_base")
967
+
968
+ all_kbs = []
969
+ for id in all_kb_ids:
970
+ try:
971
+ all_kbs.append(self.get_knowledge_base_client().get_by_id(id))
972
+ except:
973
+ continue
974
+
975
+ kb_lut = self._construct_lut_agent_resource(all_kbs, "id", "name")
976
+
977
+ for agent in new_agents:
978
+ kb_names = self._lookup_agent_resource_value(agent, kb_lut, "knowledge_base", "Knowledge Base")
979
+ if kb_names:
980
+ agent.knowledge_base = kb_names
981
+ return new_agents
982
+
983
+ def _bulk_resolve_agent_collaborators(self, agents: List[Agent]) -> List[Agent]:
984
+ new_agents = agents.copy()
985
+ all_collab_ids = self._get_all_unique_agent_resources(new_agents, "collaborators")
986
+ if not all_collab_ids:
987
+ return new_agents
988
+
989
+ native_agents = self._batch_request_resource(self.get_native_client().get_drafts_by_ids, all_collab_ids)
990
+ external_agents = self._batch_request_resource(self.get_external_client().get_drafts_by_ids, all_collab_ids)
991
+ assitant_agents = self._batch_request_resource(self.get_assistant_client().get_drafts_by_ids, all_collab_ids)
992
+
993
+ all_collabs = native_agents + external_agents + assitant_agents
994
+
995
+ collab_lut = self._construct_lut_agent_resource(all_collabs, "id", "name")
996
+
997
+ for agent in new_agents:
998
+ collab_names = self._lookup_agent_resource_value(agent, collab_lut, "collaborators", "Collaborator")
999
+ if collab_names:
1000
+ agent.collaborators = collab_names
1001
+ return new_agents
1002
+
1003
+ def _bulk_resolve_agent_app_ids(self , agents: List[ExternalAgent]) -> List[ExternalAgent]:
1004
+ new_agents = agents.copy()
1005
+ all_conn_ids = self._get_all_unique_agent_resources(new_agents, "connection_id")
1006
+ if not all_conn_ids:
1007
+ return new_agents
1008
+
1009
+ all_connections = self._batch_request_resource(get_connections_client().get_drafts_by_ids, all_conn_ids)
1010
+
1011
+ connection_lut = self._construct_lut_agent_resource(all_connections, "connection_id", "app_id")
1012
+
1013
+ for agent in new_agents:
1014
+ app_id = self._lookup_agent_resource_value(agent, connection_lut, "connection_id", "Connection")
1015
+ if app_id:
1016
+ agent.app_id = app_id
1017
+ return new_agents
1018
+
1019
+ # TODO: Make a shared util
1020
+ def _rich_table_to_markdown(self, table: rich.table.Table) -> str:
1021
+ headers = [column.header for column in table.columns]
1022
+ cols = [[cell for cell in col.cells] for col in table.columns]
1023
+ rows = list(map(list, zip(*cols)))
1024
+
1025
+ # Header row
1026
+ md = "| " + " | ".join(headers) + " |\n"
1027
+ # Separator row
1028
+ md += "| " + " | ".join(["---"] * len(headers)) + " |\n"
1029
+ # # Data rows
1030
+ for row in rows:
1031
+ md += "| " + " | ".join(row) + " |\n"
1032
+ return md
1033
+
1034
+
1035
+
1036
+ def list_agents(self, kind: AgentKind=None, verbose: bool=False, format: AgentListFormats | None = None) -> dict[str, dict] | None:
1037
+ """
1038
+ List agents in the active wxo environment
1039
+
1040
+ Args:
1041
+ kind: Filter to only list a certain kind of agent. Allowed values "native", "assistant", "external"
1042
+ verbose: Show raw json output without table formatting or id to name resolution
1043
+ format: Optional value. If provided print nothing and return a string containing the agents in the requested format. Allowed values "table", "json"
1044
+ """
1045
+ if verbose and format:
1046
+ logger.error("For agents list, `--verbose` and `--format` are mutually exclusive options")
1047
+ sys.exit(1)
1048
+
1049
+ parse_errors = []
1050
+ output_dictionary = {
1051
+ "native": None,
1052
+ "assistant": None,
1053
+ "external": None
1054
+ }
817
1055
 
818
1056
  if kind == AgentKind.NATIVE or kind is None:
819
- response = self.get_native_client().get()
820
- native_agents = []
821
- for agent in response:
822
- try:
823
- native_agents.append(Agent.model_validate(agent))
824
- except Exception as e:
825
- name = agent.get('name', None)
826
- parse_errors.append([
827
- f"Agent '{name}' could not be parsed",
828
- json.dumps(agent),
829
- e
830
- ])
1057
+ native_agents, new_parse_errors = self._fetch_and_parse_agents(AgentKind.NATIVE)
1058
+ parse_errors += new_parse_errors
831
1059
 
832
1060
  if verbose:
833
1061
  agents_list = []
834
1062
  for agent in native_agents:
835
1063
  agents_list.append(json.loads(agent.dumps_spec()))
836
1064
 
837
- verbose_output_dictionary["native"] = agents_list
1065
+ output_dictionary["native"] = agents_list
838
1066
  else:
839
- native_table = rich.table.Table(
840
- show_header=True,
841
- header_style="bold white",
842
- title="Agents",
843
- show_lines=True
844
- )
845
- column_args = {
846
- "Name": {"overflow": "fold"},
847
- "Description": {},
848
- "LLM": {"overflow": "fold"},
849
- "Style": {},
850
- "Collaborators": {},
851
- "Tools": {},
852
- "Knowledge Base": {},
853
- "ID": {"overflow": "fold"},
854
- }
855
- for column in column_args:
856
- native_table.add_column(column, **column_args[column])
857
-
858
- for agent in native_agents:
859
- tool_names = self.get_agent_tool_names(agent.tools)
860
- knowledge_base_names = self.get_agent_knowledge_base_names(agent.knowledge_base)
861
- collaborator_names = self.get_agent_collaborator_names(agent.collaborators)
862
-
863
- native_table.add_row(
864
- agent.name,
865
- agent.description,
866
- agent.llm,
867
- agent.style,
868
- ", ".join(collaborator_names),
869
- ", ".join(tool_names),
870
- ", ".join(knowledge_base_names),
871
- agent.id,
1067
+ resolved_native_agents = self._bulk_resolve_agent_tools(native_agents)
1068
+ resolved_native_agents = self._bulk_resolve_agent_knowledge_bases(resolved_native_agents)
1069
+ resolved_native_agents = self._bulk_resolve_agent_collaborators(resolved_native_agents)
1070
+
1071
+ if format and format == AgentListFormats.JSON:
1072
+ agents_list = []
1073
+ for agent in resolved_native_agents:
1074
+ agents_list.append(json.loads(agent.dumps_spec()))
1075
+
1076
+ output_dictionary["native"] = agents_list
1077
+ else:
1078
+ native_table = rich.table.Table(
1079
+ show_header=True,
1080
+ header_style="bold white",
1081
+ title="Agents",
1082
+ show_lines=True
872
1083
  )
873
- rich.print(native_table)
874
1084
 
1085
+ column_args = {
1086
+ "Name": {"overflow": "fold"},
1087
+ "Description": {},
1088
+ "LLM": {"overflow": "fold"},
1089
+ "Style": {},
1090
+ "Collaborators": {},
1091
+ "Tools": {},
1092
+ "Knowledge Base": {},
1093
+ "ID": {"overflow": "fold"},
1094
+ }
1095
+ for column in column_args:
1096
+ native_table.add_column(column, **column_args[column])
1097
+
1098
+ for agent in resolved_native_agents:
1099
+ native_table.add_row(
1100
+ agent.name,
1101
+ agent.description,
1102
+ agent.llm,
1103
+ agent.style,
1104
+ ", ".join(agent.collaborators),
1105
+ ", ".join(agent.tools),
1106
+ ", ".join(agent.knowledge_base),
1107
+ agent.id,
1108
+ )
1109
+ if format == AgentListFormats.Table:
1110
+ output_dictionary["native"] = self._rich_table_to_markdown(native_table)
1111
+ else:
1112
+ rich.print(native_table)
875
1113
 
876
1114
  if kind == AgentKind.EXTERNAL or kind is None:
877
- response = self.get_external_client().get()
878
-
879
- external_agents = []
880
- for agent in response:
881
- try:
882
- external_agents.append(ExternalAgent.model_validate(agent))
883
- except Exception as e:
884
- name = agent.get('name', None)
885
- parse_errors.append([f"External Agent {name} could not be parsed", e])
886
-
887
- response_dict = {agent["id"]: agent for agent in response}
888
-
889
- # Insert config values into config as config object is not retruned from api
890
- for external_agent in external_agents:
891
- if external_agent.id in response_dict:
892
- response_data = response_dict[external_agent.id]
893
- external_agent.config.enable_cot = response_data.get("enable_cot", external_agent.config.enable_cot)
894
- external_agent.config.hidden = response_data.get("hidden", external_agent.config.hidden)
895
-
896
- external_agents_list = []
1115
+ external_agents, new_parse_errors = self._fetch_and_parse_agents(AgentKind.EXTERNAL)
1116
+ parse_errors += new_parse_errors
1117
+
897
1118
  if verbose:
1119
+ external_agents_list = []
898
1120
  for agent in external_agents:
899
1121
  external_agents_list.append(json.loads(agent.dumps_spec()))
900
- verbose_output_dictionary["external"] = external_agents_list
1122
+ output_dictionary["external"] = external_agents_list
901
1123
  else:
902
- external_table = rich.table.Table(
903
- show_header=True,
904
- header_style="bold white",
905
- title="External Agents",
906
- show_lines=True
907
- )
908
- column_args = {
909
- "Name": {"overflow": "fold"},
910
- "Title": {},
911
- "Description": {},
912
- "Tags": {},
913
- "API URL": {"overflow": "fold"},
914
- "Chat Params": {},
915
- "Config": {},
916
- "Nickname": {},
917
- "App ID": {"overflow": "fold"},
918
- "ID": {"overflow": "fold"}
919
- }
920
-
921
- for column in column_args:
922
- external_table.add_column(column, **column_args[column])
1124
+ resolved_external_agents = self._bulk_resolve_agent_app_ids(external_agents)
923
1125
 
924
- for agent in external_agents:
925
- connections_client = get_connections_client()
926
- app_id = connections_client.get_draft_by_id(agent.connection_id)
927
-
928
- external_table.add_row(
929
- agent.name,
930
- agent.title,
931
- agent.description,
932
- ", ".join(agent.tags or []),
933
- agent.api_url,
934
- json.dumps(agent.chat_params),
935
- str(agent.config),
936
- agent.nickname,
937
- app_id,
938
- agent.id
1126
+ if format and format == AgentListFormats.JSON:
1127
+ external_agents_list = []
1128
+ for agent in resolved_external_agents:
1129
+ external_agents_list.append(json.loads(agent.dumps_spec()))
1130
+
1131
+ output_dictionary["external"] = external_agents_list
1132
+ else:
1133
+ external_table = rich.table.Table(
1134
+ show_header=True,
1135
+ header_style="bold white",
1136
+ title="External Agents",
1137
+ show_lines=True
939
1138
  )
940
- rich.print(external_table)
1139
+ column_args = {
1140
+ "Name": {"overflow": "fold"},
1141
+ "Title": {},
1142
+ "Description": {},
1143
+ "Tags": {},
1144
+ "API URL": {"overflow": "fold"},
1145
+ "Chat Params": {},
1146
+ "Config": {},
1147
+ "Nickname": {},
1148
+ "App ID": {"overflow": "fold"},
1149
+ "ID": {"overflow": "fold"}
1150
+ }
1151
+
1152
+ for column in column_args:
1153
+ external_table.add_column(column, **column_args[column])
1154
+
1155
+ for agent in external_agents:
1156
+ connections_client = get_connections_client()
1157
+ app_id = connections_client.get_draft_by_id(agent.connection_id)
1158
+ resolved_native_agents = self._bulk_resolve_agent_app_ids(external_agents)
1159
+
1160
+ external_table.add_row(
1161
+ agent.name,
1162
+ agent.title,
1163
+ agent.description,
1164
+ ", ".join(agent.tags or []),
1165
+ agent.api_url,
1166
+ json.dumps(agent.chat_params),
1167
+ str(agent.config),
1168
+ agent.nickname,
1169
+ app_id,
1170
+ agent.id
1171
+ )
1172
+ if format == AgentListFormats.Table:
1173
+ output_dictionary["external"] = self._rich_table_to_markdown(external_table)
1174
+ else:
1175
+ rich.print(external_table)
941
1176
 
942
1177
  if kind == AgentKind.ASSISTANT or kind is None:
943
- response = self.get_assistant_client().get()
944
-
945
- assistant_agents = []
946
- for agent in response:
947
- try:
948
- assistant_agents.append(AssistantAgent.model_validate(agent))
949
- except Exception as e:
950
- name = agent.get('name', None)
951
- parse_errors.append([f"Assistant Agent {name} could not be parsed", e])
952
-
953
- response_dict = {agent["id"]: agent for agent in response}
954
-
955
- # Insert config values into config as config object is not retruned from api
956
- for assistant_agent in assistant_agents:
957
- if assistant_agent.id in response_dict:
958
- response_data = response_dict[assistant_agent.id]
959
- assistant_agent.config.api_version = response_data.get("api_version", assistant_agent.config.api_version)
960
- assistant_agent.config.assistant_id = response_data.get("assistant_id", assistant_agent.config.assistant_id)
961
- assistant_agent.config.crn = response_data.get("crn", assistant_agent.config.crn)
962
- assistant_agent.config.service_instance_url = response_data.get("service_instance_url", assistant_agent.config.service_instance_url)
963
- assistant_agent.config.environment_id = response_data.get("environment_id", assistant_agent.config.environment_id)
964
- assistant_agent.config.authorization_url = response_data.get("authorization_url", assistant_agent.config.authorization_url)
1178
+ assistant_agents, new_parse_errors = self._fetch_and_parse_agents(AgentKind.ASSISTANT)
1179
+ parse_errors += new_parse_errors
965
1180
 
966
1181
  if verbose:
967
- assistant_agent_specs = []
1182
+ assistant_agents_list = []
968
1183
  for agent in assistant_agents:
969
- assistant_agent_specs.append(json.loads(agent.dumps_spec()))
970
- verbose_output_dictionary["assistant"] = assistant_agent_specs
1184
+ assistant_agents_list.append(json.loads(agent.dumps_spec()))
1185
+ output_dictionary["assistant"] = assistant_agents_list
971
1186
  else:
972
- assistants_table = rich.table.Table(
973
- show_header=True,
974
- header_style="bold white",
975
- title="Assistant Agents",
976
- show_lines=True)
977
- column_args = {
978
- "Name": {"overflow": "fold"},
979
- "Title": {},
980
- "Description": {},
981
- "Tags": {},
982
- "Nickname": {},
983
- "CRN": {},
984
- "Instance URL": {},
985
- "Assistant ID": {"overflow": "fold"},
986
- "Environment ID": {"overflow": "fold"},
987
- "ID": {"overflow": "fold"}
988
- }
1187
+ resolved_external_agents = self._bulk_resolve_agent_app_ids(assistant_agents)
989
1188
 
990
- for column in column_args:
991
- assistants_table.add_column(column, **column_args[column])
992
-
993
- for agent in assistant_agents:
994
- assistants_table.add_row(
995
- agent.name,
996
- agent.title,
997
- agent.description,
998
- ", ".join(agent.tags or []),
999
- agent.nickname,
1000
- agent.config.crn,
1001
- agent.config.service_instance_url,
1002
- agent.config.assistant_id,
1003
- agent.config.environment_id,
1004
- agent.id
1005
- )
1006
- rich.print(assistants_table)
1189
+ if format and format == AgentListFormats.JSON:
1190
+ assistant_agents_list = []
1191
+ for agent in resolved_external_agents:
1192
+ assistant_agents_list.append(json.loads(agent.dumps_spec()))
1193
+
1194
+ output_dictionary["assistant"] = assistant_agents_list
1195
+ else:
1196
+ assistants_table = rich.table.Table(
1197
+ show_header=True,
1198
+ header_style="bold white",
1199
+ title="Assistant Agents",
1200
+ show_lines=True)
1201
+ column_args = {
1202
+ "Name": {"overflow": "fold"},
1203
+ "Title": {},
1204
+ "Description": {},
1205
+ "Tags": {},
1206
+ "Nickname": {},
1207
+ "CRN": {},
1208
+ "Instance URL": {},
1209
+ "Assistant ID": {"overflow": "fold"},
1210
+ "Environment ID": {"overflow": "fold"},
1211
+ "ID": {"overflow": "fold"}
1212
+ }
1213
+
1214
+ for column in column_args:
1215
+ assistants_table.add_column(column, **column_args[column])
1216
+
1217
+ for agent in assistant_agents:
1218
+ assistants_table.add_row(
1219
+ agent.name,
1220
+ agent.title,
1221
+ agent.description,
1222
+ ", ".join(agent.tags or []),
1223
+ agent.nickname,
1224
+ agent.config.crn,
1225
+ agent.config.service_instance_url,
1226
+ agent.config.assistant_id,
1227
+ agent.config.environment_id,
1228
+ agent.id
1229
+ )
1230
+ if format == AgentListFormats.Table:
1231
+ output_dictionary["assistant"] = self._rich_table_to_markdown(assistants_table)
1232
+ else:
1233
+ rich.print(assistants_table)
1007
1234
 
1008
1235
  if verbose:
1009
- rich.print_json(data=verbose_output_dictionary)
1236
+ rich.print_json(data=output_dictionary)
1010
1237
 
1011
1238
  for error in parse_errors:
1012
1239
  for l in error:
1013
1240
  logger.error(l)
1241
+
1242
+ if verbose or format:
1243
+ return output_dictionary
1244
+
1014
1245
 
1015
1246
  def remove_agent(self, name: str, kind: AgentKind):
1016
1247
  try:
@@ -1078,7 +1309,7 @@ class AgentsController:
1078
1309
  return AssistantAgent.model_validate(assistant_result)
1079
1310
 
1080
1311
 
1081
- def export_agent(self, name: str, kind: AgentKind, output_path: str, agent_only_flag: bool=False, zip_file_out: zipfile.ZipFile | None = None) -> None:
1312
+ def export_agent(self, name: str, kind: AgentKind, output_path: str, agent_only_flag: bool=False, zip_file_out: zipfile.ZipFile | None = None, with_tool_spec_file: bool = False) -> None:
1082
1313
  output_file = Path(output_path)
1083
1314
  output_file_extension = output_file.suffix
1084
1315
  output_file_name = output_file.stem
@@ -1126,15 +1357,22 @@ class AgentsController:
1126
1357
  agent_spec_yaml_file.getvalue()
1127
1358
  )
1128
1359
 
1129
- tools_contoller = ToolsController()
1130
- for tool_name in agent_spec_file_content.get("tools", []):
1360
+ agent_tools = agent_spec_file_content.get("tools", [])
1361
+
1362
+ tools_controller = ToolsController()
1363
+ tools_client = tools_controller.get_client()
1364
+ tool_specs = None
1365
+ if with_tool_spec_file:
1366
+ tool_specs = {t.get('name'):t for t in tools_client.get_drafts_by_names(agent_tools) if t.get('name')}
1367
+
1368
+ for tool_name in agent_tools:
1131
1369
 
1132
1370
  base_tool_file_path = f"{output_file_name}/tools/{tool_name}/"
1133
1371
  if check_file_in_zip(file_path=base_tool_file_path, zip_file=zip_file_out):
1134
1372
  continue
1135
1373
 
1136
1374
  logger.info(f"Exporting tool '{tool_name}'")
1137
- tool_artifact_bytes = tools_contoller.download_tool(tool_name)
1375
+ tool_artifact_bytes = tools_controller.download_tool(tool_name)
1138
1376
  if not tool_artifact_bytes:
1139
1377
  continue
1140
1378
 
@@ -1146,6 +1384,12 @@ class AgentsController:
1146
1384
  f"{base_tool_file_path}{item.filename}",
1147
1385
  buffer
1148
1386
  )
1387
+ if with_tool_spec_file and tool_specs:
1388
+ current_spec = tool_specs[tool_name]
1389
+ zip_file_out.writestr(
1390
+ f"{base_tool_file_path}config.json",
1391
+ ToolSpec.model_validate(current_spec).model_dump_json(exclude_unset=True,indent=2)
1392
+ )
1149
1393
 
1150
1394
  for kb_name in agent_spec_file_content.get("knowledge_base", []):
1151
1395
  logger.warning(f"Skipping {kb_name}, knowledge_bases are currently unsupported by export")