ibm-watsonx-orchestrate 1.12.0b0__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 (66) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -5
  3. ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
  4. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +11 -2
  5. ibm_watsonx_orchestrate/agent_builder/models/types.py +18 -1
  6. ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +1 -1
  7. ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
  8. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
  9. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +1 -1
  10. ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +61 -1
  11. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +6 -0
  12. ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
  13. ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
  14. ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +29 -53
  16. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
  17. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -30
  18. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +25 -2
  19. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +249 -14
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
  21. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +5 -1
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -3
  23. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +3 -2
  24. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
  25. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +45 -16
  26. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +23 -4
  27. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
  29. ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +21 -4
  30. ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +7 -15
  31. ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +30 -20
  33. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +2 -2
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
  36. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +79 -36
  37. ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
  38. ibm_watsonx_orchestrate/cli/common.py +26 -0
  39. ibm_watsonx_orchestrate/cli/config.py +33 -2
  40. ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
  41. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +34 -1
  42. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
  43. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
  44. ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
  45. ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
  46. ibm_watsonx_orchestrate/client/utils.py +29 -7
  47. ibm_watsonx_orchestrate/docker/compose-lite.yml +58 -8
  48. ibm_watsonx_orchestrate/docker/default.env +26 -17
  49. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +10 -2
  50. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +90 -16
  51. ibm_watsonx_orchestrate/flow_builder/node.py +14 -2
  52. ibm_watsonx_orchestrate/flow_builder/types.py +57 -3
  53. ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
  54. ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
  55. ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
  56. ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
  57. ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
  58. ibm_watsonx_orchestrate/utils/environment.py +165 -20
  59. ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
  60. ibm_watsonx_orchestrate/utils/tokens.py +51 -0
  61. ibm_watsonx_orchestrate/utils/utils.py +63 -4
  62. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/METADATA +2 -2
  63. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/RECORD +66 -59
  64. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/WHEEL +0 -0
  65. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/entry_points.txt +0 -0
  66. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -1,8 +1,8 @@
1
1
  import os
2
2
  import zipfile
3
3
  import tempfile
4
- from typing import List, Optional
5
- from enum import Enum
4
+ from typing import List, Optional, Any
5
+ from pydantic import BaseModel
6
6
  import logging
7
7
  import sys
8
8
  import re
@@ -10,7 +10,7 @@ import requests
10
10
  from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
11
11
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
12
12
  from ibm_watsonx_orchestrate.agent_builder.toolkits.base_toolkit import BaseToolkit, ToolkitSpec
13
- from ibm_watsonx_orchestrate.agent_builder.toolkits.types import ToolkitKind, Language, ToolkitSource, ToolkitTransportKind
13
+ from ibm_watsonx_orchestrate.agent_builder.toolkits.types import ToolkitKind, Language, ToolkitSource, ToolkitTransportKind, ToolkitListEntry
14
14
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
15
15
  from ibm_watsonx_orchestrate.utils.utils import sanitize_app_id
16
16
  from ibm_watsonx_orchestrate.client.connections import get_connections_client
@@ -18,7 +18,7 @@ import typer
18
18
  import json
19
19
  from rich.console import Console
20
20
  from rich.progress import Progress, SpinnerColumn, TextColumn
21
- from ibm_watsonx_orchestrate.client.utils import is_local_dev
21
+ from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
22
22
  from rich.json import JSON
23
23
  import rich
24
24
  import rich.table
@@ -264,8 +264,119 @@ class ToolkitController:
264
264
  except requests.HTTPError as e:
265
265
  logger.error(e.response.text)
266
266
  exit(1)
267
+
268
+ def _lookup_toolkit_resource_value(
269
+ self,
270
+ toolkit: BaseToolkit,
271
+ lookup_table: dict[str, str],
272
+ target_attr: str,
273
+ target_attr_display_name: str
274
+ ) -> List[str] | str | None:
275
+ """
276
+ Using a lookup table convert all the strings in a given field of an agent into their equivalent in the lookup table
277
+ Example: lookup_table={1: obj1, 2: obj2} agent=Toolkit(tools=[1,2]) return. [obj1, obj2]
278
+
279
+ Args:
280
+ toolkit: A toolkit
281
+ lookup_table: A dictionary that maps one value to another
282
+ target_attr: The field to convert on the provided agent
283
+ target_attr_display_name: The name of the field to be displayed in the event of an error
284
+ """
285
+ attr_value = getattr(toolkit, target_attr, None)
286
+ if not attr_value:
287
+ return
288
+
289
+ if isinstance(attr_value, list):
290
+ new_resource_list=[]
291
+ for value in attr_value:
292
+ if value in lookup_table:
293
+ new_resource_list.append(lookup_table[value])
294
+ else:
295
+ logger.warning(f"{target_attr_display_name} with ID '{value}' not found. Returning {target_attr_display_name} ID")
296
+ new_resource_list.append(value)
297
+ return new_resource_list
298
+ else:
299
+ if attr_value in lookup_table:
300
+ return lookup_table[attr_value]
301
+ else:
302
+ logger.warning(f"{target_attr_display_name} with ID '{attr_value}' not found. Returning {target_attr_display_name} ID")
303
+ return attr_value
304
+
305
+ def _construct_lut_toolkit_resource(self, resource_list: List[dict], key_attr: str, value_attr) -> dict:
306
+ """
307
+ Given a list of dictionaries build a key -> value look up table
308
+ Example [{id: 1, name: obj1}, {id: 2, name: obj2}] return {1: obj1, 2: obj2}
309
+
310
+ Args:
311
+ resource_list: A list of dictionries from which to build the lookup table from
312
+ key_attr: The name of the field whose value will form the key of the lookup table
313
+ value_attrL The name of the field whose value will form the value of the lookup table
314
+
315
+ Returns:
316
+ A lookup table
317
+ """
318
+ lut = {}
319
+ for resource in resource_list:
320
+ if isinstance(resource, BaseModel):
321
+ resource = resource.model_dump()
322
+ lut[resource.get(key_attr, None)] = resource.get(value_attr, None)
323
+ return lut
324
+
325
+ def _batch_request_resource(self, client_fn, ids, batch_size=50) -> List[dict]:
326
+ resources = []
327
+ for i in range(0, len(ids), batch_size):
328
+ chunk = ids[i:i + batch_size]
329
+ resources += (client_fn(chunk))
330
+ return resources
331
+
332
+ def _get_all_unique_toolkit_resources(self, toolkits: List[BaseToolkit], target_attr: str) -> List[str]:
333
+ """
334
+ Given a list of toolkits get all the unique values of a certain field
335
+ Example: tk1.tools = [1 ,2 ,3] and tk2.tools = [2, 4, 5] then return [1, 2, 3, 4, 5]
336
+ Example: tk1.id = "123" and tk2.id = "456" then return ["123", "456"]
337
+
338
+ Args:
339
+ toolkits: List of toolkits
340
+ target_attr: The name of the field to access and get unique elements
341
+
342
+ Returns:
343
+ A list of unique elements from across all toolkits
344
+ """
345
+ all_ids = set()
346
+ for toolkit in toolkits:
347
+ attr_value = getattr(toolkit, target_attr, None)
348
+ if attr_value:
349
+ if isinstance(attr_value, list):
350
+ all_ids.update(attr_value)
351
+ else:
352
+ all_ids.add(attr_value)
353
+ return list(all_ids)
354
+
355
+ def _bulk_resolve_toolkit_tools(self, toolkits: List[BaseToolkit]) -> List[BaseToolkit]:
356
+ new_toolkit_specs = [tk.__toolkit_spec__ for tk in toolkits].copy()
357
+ all_tools_ids = self._get_all_unique_toolkit_resources(new_toolkit_specs, "tools")
358
+ if not all_tools_ids:
359
+ return toolkits
360
+
361
+ tool_client = instantiate_client(ToolClient)
362
+
363
+ all_tools = self._batch_request_resource(tool_client.get_drafts_by_ids, all_tools_ids)
364
+
365
+ tool_lut = self._construct_lut_toolkit_resource(all_tools, "id", "name")
366
+
367
+ new_toolkits = []
368
+ for toolkit_spec in new_toolkit_specs:
369
+ tool_names = self._lookup_toolkit_resource_value(toolkit_spec, tool_lut, "tools", "Tool")
370
+ if tool_names:
371
+ toolkit_spec.tools = tool_names
372
+ new_toolkits.append(BaseToolkit(toolkit_spec))
373
+ return new_toolkits
374
+
375
+ def list_toolkits(self, verbose=False, format: ListFormats| None = None) -> List[dict[str, Any]] | List[ToolkitListEntry] | str | None:
376
+ if verbose and format:
377
+ logger.error("For toolkits list, `--verbose` and `--format` are mutually exclusive options")
378
+ sys.exit(1)
267
379
 
268
- def list_toolkits(self, verbose=False):
269
380
  client = self.get_client()
270
381
  response = client.get()
271
382
  toolkit_spec = [ToolkitSpec.model_validate(toolkit) for toolkit in response]
@@ -276,7 +387,10 @@ class ToolkitController:
276
387
  for toolkit in toolkits:
277
388
  tools_list.append(json.loads(toolkit.dumps_spec()))
278
389
  rich.print(JSON(json.dumps(tools_list, indent=4)))
390
+ return tools_list
279
391
  else:
392
+ toolkit_details = []
393
+
280
394
  table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
281
395
  column_args = {
282
396
  "Name": {"overflow": "fold"},
@@ -288,23 +402,14 @@ class ToolkitController:
288
402
  for column in column_args:
289
403
  table.add_column(column,**column_args[column])
290
404
 
291
- tools_client = instantiate_client(ToolClient)
292
-
293
405
  connections_client = get_connections_client()
294
406
  connections = connections_client.list()
295
407
 
296
408
  connections_dict = {conn.connection_id: conn for conn in connections}
297
409
 
298
- for toolkit in toolkits:
299
- tool_ids = toolkit.__toolkit_spec__.tools or []
300
- tool_names = []
301
- if len(tool_ids) == 0:
302
- logger.warning("This toolkit contains no tools.")
303
-
304
- for tool_id in tool_ids:
305
- tool = tools_client.get_draft_by_id(tool_id)
306
- tool_names.append(tool["name"])
410
+ resolved_toolkits = self._bulk_resolve_toolkit_tools(toolkits)
307
411
 
412
+ for toolkit in resolved_toolkits:
308
413
  app_ids = []
309
414
  connection_ids = toolkit.__toolkit_spec__.mcp.connections.values()
310
415
 
@@ -317,15 +422,22 @@ class ToolkitController:
317
422
  else:
318
423
  app_id = ""
319
424
  app_ids.append(app_id)
320
-
321
-
322
-
323
- table.add_row(
324
- toolkit.__toolkit_spec__.name,
325
- "MCP",
326
- toolkit.__toolkit_spec__.description,
327
- ", ".join(tool_names),
328
- ", ".join(app_ids),
425
+
426
+ entry = ToolkitListEntry(
427
+ name = toolkit.__toolkit_spec__.name,
428
+ description = toolkit.__toolkit_spec__.description,
429
+ tools = toolkit.__toolkit_spec__.tools,
430
+ app_ids = app_ids
329
431
  )
330
-
331
- rich.print(table)
432
+ if format == ListFormats.JSON:
433
+ toolkit_details.append(entry)
434
+ else:
435
+ table.add_row(*entry.get_row_details())
436
+
437
+ match format:
438
+ case ListFormats.JSON:
439
+ return toolkit_details
440
+ case ListFormats.Table:
441
+ return rich_table_to_markdown(table)
442
+ case _:
443
+ rich.print(table)
@@ -1,6 +1,6 @@
1
1
  import typer
2
2
  from typing import List
3
- from typing_extensions import Annotated
3
+ from typing_extensions import Annotated, Optional
4
4
  from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import ToolsController, ToolKind
5
5
  tools_app= typer.Typer(no_args_is_help=True)
6
6
 
@@ -34,7 +34,7 @@ def tool_import(
34
34
  )
35
35
  ] = None,
36
36
  requirements_file: Annotated[
37
- str,
37
+ Optional[str],
38
38
  typer.Option(
39
39
  "--requirements-file",
40
40
  "-r",
@@ -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