ibm-watsonx-orchestrate 1.12.2__py3-none-any.whl → 1.13.0b1__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.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +13 -2
- ibm_watsonx_orchestrate/agent_builder/models/types.py +17 -1
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +31 -53
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +54 -28
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +36 -2
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +270 -26
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +30 -3
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_environment_manager.py +158 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +26 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +150 -34
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +50 -18
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +43 -29
- ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
- ibm_watsonx_orchestrate/cli/common.py +26 -0
- ibm_watsonx_orchestrate/cli/config.py +30 -1
- ibm_watsonx_orchestrate/client/agents/agent_client.py +1 -1
- ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +55 -11
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
- ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
- ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +4 -2
- ibm_watsonx_orchestrate/client/utils.py +29 -7
- ibm_watsonx_orchestrate/docker/compose-lite.yml +3 -2
- ibm_watsonx_orchestrate/docker/default.env +15 -10
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +28 -12
- ibm_watsonx_orchestrate/flow_builder/types.py +25 -0
- ibm_watsonx_orchestrate/flow_builder/utils.py +1 -9
- ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
- ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
- ibm_watsonx_orchestrate/utils/environment.py +165 -20
- ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
- ibm_watsonx_orchestrate/utils/tokens.py +51 -0
- ibm_watsonx_orchestrate/utils/utils.py +57 -2
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/RECORD +53 -48
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -16,7 +16,7 @@ from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
|
16
16
|
from ibm_watsonx_orchestrate.cli.commands.environment.environment_controller import _login
|
17
17
|
|
18
18
|
from ibm_watsonx_orchestrate.cli.config import PROTECTED_ENV_NAME, clear_protected_env_credentials_token, Config, \
|
19
|
-
AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER,
|
19
|
+
AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, LICENSE_HEADER, \
|
20
20
|
ENV_ACCEPT_LICENSE
|
21
21
|
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
|
22
22
|
from ibm_watsonx_orchestrate.utils.docker_utils import DockerLoginService, DockerComposeCore, DockerUtils
|
@@ -38,8 +38,12 @@ def refresh_local_credentials() -> None:
|
|
38
38
|
"""
|
39
39
|
Refresh the local credentials
|
40
40
|
"""
|
41
|
-
|
42
|
-
|
41
|
+
try:
|
42
|
+
clear_protected_env_credentials_token()
|
43
|
+
_login(name=PROTECTED_ENV_NAME, apikey=None)
|
44
|
+
|
45
|
+
except:
|
46
|
+
logger.warning("Failed to refresh local credentials, please run `orchestrate env activate local`")
|
43
47
|
|
44
48
|
def run_compose_lite(
|
45
49
|
final_env_file: Path,
|
@@ -51,11 +55,11 @@ def run_compose_lite(
|
|
51
55
|
with_connections_ui=False,
|
52
56
|
with_langflow=False,
|
53
57
|
) -> None:
|
54
|
-
|
55
|
-
db_tag =
|
58
|
+
env_service.prepare_clean_env(final_env_file)
|
59
|
+
db_tag = env_service.read_env_file(final_env_file).get('DBTAG', None)
|
56
60
|
logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
|
57
61
|
|
58
|
-
compose_core = DockerComposeCore(env_service)
|
62
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
59
63
|
|
60
64
|
# Step 1: Start only the DB container
|
61
65
|
result = compose_core.service_up(service_name="wxo-server-db", friendly_name="WxO Server DB", final_env_file=final_env_file, compose_env=os.environ)
|
@@ -197,7 +201,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
197
201
|
logger.error("Healthcheck failed orchestrate server. Make sure you start the server components with `orchestrate server start` before trying to start the chat UI")
|
198
202
|
return False
|
199
203
|
|
200
|
-
compose_core = DockerComposeCore(env_service)
|
204
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
201
205
|
|
202
206
|
result = compose_core.service_up(service_name="ui", friendly_name="UI", final_env_file=final_env_file)
|
203
207
|
|
@@ -234,7 +238,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
|
|
234
238
|
|
235
239
|
cli_config = Config()
|
236
240
|
env_service = EnvService(cli_config)
|
237
|
-
compose_core = DockerComposeCore(env_service)
|
241
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
238
242
|
|
239
243
|
result = compose_core.service_down(service_name="ui", friendly_name="UI", final_env_file=final_env_file, is_reset=is_reset)
|
240
244
|
|
@@ -255,7 +259,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
|
|
255
259
|
|
256
260
|
cli_config = Config()
|
257
261
|
env_service = EnvService(cli_config)
|
258
|
-
compose_core = DockerComposeCore(env_service)
|
262
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
259
263
|
|
260
264
|
result = compose_core.services_down(final_env_file=final_env_file, is_reset=is_reset)
|
261
265
|
|
@@ -276,7 +280,7 @@ def run_compose_lite_logs(final_env_file: Path) -> None:
|
|
276
280
|
|
277
281
|
cli_config = Config()
|
278
282
|
env_service = EnvService(cli_config)
|
279
|
-
compose_core = DockerComposeCore(env_service)
|
283
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
280
284
|
|
281
285
|
result = compose_core.services_logs(final_env_file=final_env_file, should_follow=True)
|
282
286
|
|
@@ -395,7 +399,8 @@ def server_start(
|
|
395
399
|
env_service.set_compose_file_path_in_env(custom_compose_file)
|
396
400
|
|
397
401
|
user_env = env_service.get_user_env(user_env_file=user_env_file, fallback_to_persisted_env=False)
|
398
|
-
env_service.
|
402
|
+
developer_edition_source = env_service.get_dev_edition_source_core(user_env)
|
403
|
+
env_service.persist_user_env(user_env, include_secrets=persist_env_secrets, source=developer_edition_source)
|
399
404
|
|
400
405
|
merged_env_dict = env_service.prepare_server_env_vars(user_env=user_env, should_drop_auth_routes=False)
|
401
406
|
|
@@ -451,10 +456,7 @@ def server_start(
|
|
451
456
|
)
|
452
457
|
exit(1)
|
453
458
|
|
454
|
-
|
455
|
-
refresh_local_credentials()
|
456
|
-
except:
|
457
|
-
logger.warning("Failed to refresh local credentials, please run `orchestrate env activate local`")
|
459
|
+
refresh_local_credentials()
|
458
460
|
|
459
461
|
logger.info(f"You can run `orchestrate env activate local` to set your environment or `orchestrate chat start` to start the UI service and begin chatting.")
|
460
462
|
|
@@ -550,7 +552,6 @@ def run_db_migration() -> None:
|
|
550
552
|
migration_command = f'''
|
551
553
|
APPLIED_MIGRATIONS_FILE="/var/lib/postgresql/applied_migrations/applied_migrations.txt"
|
552
554
|
touch "$APPLIED_MIGRATIONS_FILE"
|
553
|
-
|
554
555
|
for file in /docker-entrypoint-initdb.d/*.sql; do
|
555
556
|
filename=$(basename "$file")
|
556
557
|
|
@@ -566,11 +567,42 @@ def run_db_migration() -> None:
|
|
566
567
|
fi
|
567
568
|
fi
|
568
569
|
done
|
570
|
+
|
571
|
+
# Create wxo_observability database if it doesn't exist
|
572
|
+
if psql -U {pg_user} -lqt | cut -d \\| -f 1 | grep -qw wxo_observability; then
|
573
|
+
echo 'Existing wxo_observability DB found'
|
574
|
+
else
|
575
|
+
echo 'Creating wxo_observability DB'
|
576
|
+
createdb -U "{pg_user}" -O "{pg_user}" wxo_observability;
|
577
|
+
psql -U {pg_user} -q -d postgres -c "GRANT CONNECT ON DATABASE wxo_observability TO {pg_user}";
|
578
|
+
fi
|
579
|
+
|
580
|
+
# Run observability-specific migrations
|
581
|
+
OBSERVABILITY_MIGRATIONS_FILE="/var/lib/postgresql/applied_migrations/observability_migrations.txt"
|
582
|
+
touch "$OBSERVABILITY_MIGRATIONS_FILE"
|
583
|
+
|
584
|
+
for file in /docker-entrypoint-initdb.d/observability/*.sql; do
|
585
|
+
if [ -f "$file" ]; then
|
586
|
+
filename=$(basename "$file")
|
587
|
+
|
588
|
+
if grep -Fxq "$filename" "$OBSERVABILITY_MIGRATIONS_FILE"; then
|
589
|
+
echo "Skipping already applied observability migration: $filename"
|
590
|
+
else
|
591
|
+
echo "Applying observability migration: $filename"
|
592
|
+
if psql -U {pg_user} -d wxo_observability -q -f "$file" > /dev/null 2>&1; then
|
593
|
+
echo "$filename" >> "$OBSERVABILITY_MIGRATIONS_FILE"
|
594
|
+
else
|
595
|
+
echo "Error applying observability migration: $filename. Stopping migrations."
|
596
|
+
exit 1
|
597
|
+
fi
|
598
|
+
fi
|
599
|
+
fi
|
600
|
+
done
|
569
601
|
'''
|
570
602
|
|
571
603
|
cli_config = Config()
|
572
604
|
env_service = EnvService(cli_config)
|
573
|
-
compose_core = DockerComposeCore(env_service)
|
605
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
574
606
|
|
575
607
|
result = compose_core.service_container_bash_exec(service_name="wxo-server-db",
|
576
608
|
log_message="Running Database Migration...",
|
@@ -624,7 +656,7 @@ def create_langflow_db() -> None:
|
|
624
656
|
|
625
657
|
cli_config = Config()
|
626
658
|
env_service = EnvService(cli_config)
|
627
|
-
compose_core = DockerComposeCore(env_service)
|
659
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
628
660
|
|
629
661
|
result = compose_core.service_container_bash_exec(service_name="wxo-server-db",
|
630
662
|
log_message="Preparing Langflow resources...",
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import os
|
2
2
|
import zipfile
|
3
3
|
import tempfile
|
4
|
-
from typing import List, Optional
|
5
|
-
from
|
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.
|
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
|
-
|
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
|
-
|
324
|
-
toolkit.__toolkit_spec__.
|
325
|
-
|
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
|
-
|
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,25 +10,24 @@ 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, cast
|
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
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, \
|
@@ -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
|
|
@@ -620,7 +619,7 @@ def get_whl_in_registry(registry_url: str, version: str) -> str| None:
|
|
620
619
|
return wheel_file
|
621
620
|
|
622
621
|
class ToolsController:
|
623
|
-
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):
|
624
623
|
self.client = None
|
625
624
|
self.tool_kind = tool_kind
|
626
625
|
self.file = file
|
@@ -656,14 +655,14 @@ class ToolsController:
|
|
656
655
|
app_id = app_id[0]
|
657
656
|
connection = connections_client.get_draft_by_app_id(app_id=app_id)
|
658
657
|
connection_id = connection.connection_id
|
659
|
-
tools =
|
658
|
+
tools = run_coroutine_sync(import_openapi_tool(file=args["file"], connection_id=connection_id))
|
660
659
|
case "flow":
|
661
|
-
tools =
|
660
|
+
tools = run_coroutine_sync(import_flow_tool(file=args["file"]))
|
662
661
|
case "skill":
|
663
662
|
tools = []
|
664
663
|
logger.warning("Skill Import not implemented yet")
|
665
664
|
case "langflow":
|
666
|
-
tools =
|
665
|
+
tools = run_coroutine_sync(import_langflow_tool(file=args["file"],app_id=args.get('app_id',None)))
|
667
666
|
case _:
|
668
667
|
raise BadRequest("Invalid kind selected")
|
669
668
|
|
@@ -674,14 +673,18 @@ class ToolsController:
|
|
674
673
|
yield tool
|
675
674
|
|
676
675
|
|
677
|
-
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
|
+
|
678
681
|
response = self.get_client().get()
|
679
682
|
tool_specs = []
|
680
683
|
parse_errors = []
|
681
684
|
|
682
685
|
for tool in response:
|
683
686
|
try:
|
684
|
-
tool_specs.append(ToolSpec.model_validate(tool))
|
687
|
+
tool_specs.append(ToolSpec.model_validate(tool, context="list"))
|
685
688
|
except Exception as e:
|
686
689
|
name = tool.get('name', None)
|
687
690
|
parse_errors.append([
|
@@ -698,12 +701,19 @@ class ToolsController:
|
|
698
701
|
tools_list.append(json.loads(tool.dumps_spec()))
|
699
702
|
|
700
703
|
rich.print_json(json.dumps(tools_list, indent=4))
|
704
|
+
return tools_list
|
701
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
|
+
|
702
713
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
703
714
|
column_args = {
|
704
715
|
"Name": {"overflow": "fold"},
|
705
716
|
"Description": {},
|
706
|
-
"Permission": {},
|
707
717
|
"Type": {},
|
708
718
|
"Toolkit": {},
|
709
719
|
"App ID": {"overflow": "fold"}
|
@@ -711,11 +721,6 @@ class ToolsController:
|
|
711
721
|
for column in column_args:
|
712
722
|
table.add_column(column,**column_args[column])
|
713
723
|
|
714
|
-
connections_client = get_connections_client()
|
715
|
-
connections = connections_client.list()
|
716
|
-
|
717
|
-
connections_dict = {conn.connection_id: conn for conn in connections}
|
718
|
-
|
719
724
|
for tool in tools:
|
720
725
|
tool_binding = tool.__tool_spec__.binding
|
721
726
|
|
@@ -773,22 +778,31 @@ class ToolsController:
|
|
773
778
|
toolkit_name = toolkit["name"]
|
774
779
|
elif toolkit:
|
775
780
|
toolkit_name = str(toolkit)
|
776
|
-
|
777
781
|
|
778
|
-
|
779
|
-
tool.__tool_spec__.name,
|
780
|
-
tool.__tool_spec__.description,
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
", ".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
|
785
788
|
)
|
786
789
|
|
787
|
-
|
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)
|
788
802
|
|
789
|
-
|
790
|
-
|
791
|
-
|
803
|
+
for error in parse_errors:
|
804
|
+
for l in error:
|
805
|
+
logger.error(l)
|
792
806
|
|
793
807
|
def get_all_tools(self) -> dict:
|
794
808
|
return {entry["name"]: entry["id"] for entry in self.get_client().get()}
|
ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py
CHANGED
@@ -3,10 +3,12 @@ import sys
|
|
3
3
|
import rich
|
4
4
|
import yaml
|
5
5
|
import logging
|
6
|
-
from
|
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.
|
99
|
-
|
100
|
-
config.name,
|
101
|
-
config.voice_configuration_id,
|
102
|
-
config.speech_to_text.provider,
|
103
|
-
config.text_to_speech.provider,
|
104
|
-
|
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
|
-
|
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
|