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
@@ -6,6 +6,7 @@ from ibm_watsonx_orchestrate.cli.commands.connections.connections_controller imp
6
6
  remove_connection,
7
7
  list_connections,
8
8
  import_connection,
9
+ export_connection,
9
10
  configure_connection,
10
11
  set_credentials_connection,
11
12
  set_identity_provider_connection,
@@ -65,6 +66,23 @@ def import_connection_command(
65
66
  ):
66
67
  import_connection(file=file)
67
68
 
69
+ @connections_app.command(name="export")
70
+ def export_connection_command(
71
+ app_id: Annotated[
72
+ str, typer.Option(
73
+ '--app-id', '-a',
74
+ help='The app id of the connection you wish to export.'
75
+ )
76
+ ],
77
+ output_file: Annotated[
78
+ str, typer.Option(
79
+ '--output', '-o',
80
+ help='Path to where the exported connection should be saved.'
81
+ )
82
+ ] = None
83
+ ):
84
+ export_connection(app_id=app_id,output_file=output_file)
85
+
68
86
  @connections_app.command(name="configure")
69
87
  def configure_connection_command(
70
88
  app_id: Annotated[
@@ -1,4 +1,7 @@
1
+ import io
1
2
  import logging
3
+ from pathlib import Path
4
+ import zipfile
2
5
  import requests
3
6
  import json
4
7
  import rich
@@ -7,6 +10,7 @@ import sys
7
10
  import typer
8
11
 
9
12
  from typing import List
13
+ from ibm_watsonx_orchestrate.agent_builder.agents.types import SpecVersion
10
14
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
11
15
  from ibm_watsonx_orchestrate.agent_builder.connections.types import (
12
16
  ConnectionEnvironment,
@@ -277,6 +281,48 @@ def _connection_credentials_parse_entry(text: str, default_location: ConnectionC
277
281
 
278
282
  return ConnectionCredentialsEntry(key=key, value=value, location=location)
279
283
 
284
+ def _combine_connection_configs(configs: List[ConnectionConfiguration]) -> dict:
285
+ combined_configuration = {
286
+ 'app_id': None,
287
+ 'spec_version': SpecVersion.V1.value,
288
+ 'kind': 'connection',
289
+ 'environments': {
290
+
291
+ },
292
+ 'catalog': {
293
+ 'name': None,
294
+ 'description': None,
295
+ 'icon': None
296
+ }
297
+ }
298
+ for config in configs:
299
+ if combined_configuration.get('app_id') and config.app_id != combined_configuration.get('app_id'):
300
+ raise ValueError(f"Cannot combine config '{config.app_id}' with config '{combined_configuration.get('app_id')}'")
301
+ combined_configuration['app_id'] = config.app_id
302
+
303
+ current_env = config.environment.value.lower()
304
+ combined_configuration['environments'][current_env] = {}
305
+
306
+ for k,v in config.model_dump().items():
307
+ if not v:
308
+ continue
309
+ match(k):
310
+ case "app_id" | "environment" | "spec_version":
311
+ continue
312
+ case _:
313
+ try:
314
+ combined_configuration['environments'][current_env][k] = str(v)
315
+ except:
316
+ logger.error(f"Couldn't represent {k} as a string")
317
+
318
+ return combined_configuration
319
+
320
+ def _resolve_connection_ids(connection_ids: list[str]) -> list[str]:
321
+ client = get_connections_client()
322
+ connections = client.list()
323
+ return list(set([c.app_id for c in connections if c.connection_id in connection_ids]))
324
+
325
+
280
326
 
281
327
  def add_configuration(config: ConnectionConfiguration) -> None:
282
328
  client = get_connections_client()
@@ -396,6 +442,21 @@ def add_connection(app_id: str) -> None:
396
442
  logger.error(response_text)
397
443
  exit(1)
398
444
 
445
+ def get_connection_configs(app_id: str) -> List[ConnectionConfiguration]:
446
+ client = get_connections_client()
447
+ connection_configs = []
448
+ for env in ConnectionEnvironment:
449
+ try:
450
+ config = client.get_config(app_id=app_id,env=env)
451
+ if not config:
452
+ logger.warning(f"No {env.value.lower()} configuration found for connection '{app_id}'")
453
+ else:
454
+ connection_configs.append( config.as_config() )
455
+ except:
456
+ logger.error(f"Unable to get {env.value.lower()} configs for connection '{app_id}'")
457
+
458
+ return connection_configs
459
+
399
460
  def remove_connection(app_id: str) -> None:
400
461
  client = get_connections_client()
401
462
 
@@ -479,6 +540,59 @@ def list_connections(environment: ConnectionEnvironment | None, verbose: bool =
479
540
  def import_connection(file: str) -> None:
480
541
  _parse_file(file=file)
481
542
 
543
+ def export_connection(output_file: str, app_id: str | None = None, connection_id: str | None = None) -> None:
544
+ if not app_id and not connection_id:
545
+ raise ValueError(f"Connection export requires at least one of 'app_id' or 'connection_id'")
546
+
547
+ if app_id and connection_id:
548
+ logger.warning(f"Connection export recieved both 'app_id' and 'connection_id', preferring 'app_id'")
549
+
550
+ if not app_id:
551
+ app_ids = _resolve_connection_ids([connection_id])
552
+ if len(app_ids) > 0:
553
+ app_id = app_ids[0]
554
+ else:
555
+ raise ValueError(f"No connections found with connection_id of '{connection_id}'")
556
+
557
+
558
+ # verify output folder
559
+ output_path = Path(output_file)
560
+ if output_path.exists():
561
+ logger.error(f"Specified output file already exists")
562
+ sys.exit(0)
563
+
564
+ output_type = output_path.suffix.lower()
565
+ if output_type not in ['.zip','.yaml','.yml']:
566
+ logger.error(f"Output file must end with the extension '.zip', '.yaml' or '.yml'")
567
+ sys.exit(0)
568
+
569
+ # get connection data
570
+ connections = get_connection_configs(app_id=app_id)
571
+ combined_connections = _combine_connection_configs(connections)
572
+
573
+ # write to folder
574
+ match(output_type):
575
+ case '.zip':
576
+ zip_file = zipfile.ZipFile(output_path, "w")
577
+
578
+ connection_yaml = yaml.dump(combined_connections, sort_keys=False, default_flow_style=False)
579
+ connection_yaml_bytes = connection_yaml.encode("utf-8")
580
+ connection_yaml_file = io.BytesIO(connection_yaml_bytes)
581
+
582
+ zip_file.writestr(
583
+ f"{output_path.stem}/{app_id}.yaml",
584
+ connection_yaml_file.getvalue()
585
+ )
586
+
587
+ zip_file.close()
588
+ case '.yaml' | '.yml':
589
+ with open(output_path,'w') as yaml_file:
590
+ yaml_file.write(
591
+ yaml.dump(combined_connections, sort_keys=False, default_flow_style=False)
592
+ )
593
+
594
+ logger.info(f"Successfully exported connection file for {app_id}")
595
+
482
596
  def configure_connection(**kwargs) -> None:
483
597
  if is_local_dev() and kwargs.get("environment") != ConnectionEnvironment.DRAFT:
484
598
  logger.error(f"Cannot create configuration for environment '{kwargs.get('environment')}'. Local development does not support any environments other than 'draft'.")
@@ -1,24 +1,13 @@
1
1
  import logging
2
2
  import sys
3
3
  from pathlib import Path
4
- import subprocess
5
4
  import time
6
5
  import requests
7
6
  from urllib.parse import urlparse
8
- from ibm_watsonx_orchestrate.cli.commands.server.server_command import (
9
- get_compose_file,
10
- ensure_docker_compose_installed,
11
- _prepare_clean_env,
12
- ensure_docker_installed,
13
- read_env_file,
14
- get_default_env_file,
15
- get_persisted_user_env,
16
- get_dev_edition_source,
17
- get_default_registry_env_vars_by_dev_edition_source,
18
- docker_login_by_dev_edition_source,
19
- write_merged_env_file,
20
- apply_server_env_dict_defaults
21
- )
7
+
8
+ from ibm_watsonx_orchestrate.cli.config import Config
9
+ from ibm_watsonx_orchestrate.utils.docker_utils import DockerLoginService, DockerComposeCore, DockerUtils
10
+ from ibm_watsonx_orchestrate.utils.environment import EnvService
22
11
 
23
12
  logger = logging.getLogger(__name__)
24
13
 
@@ -39,71 +28,26 @@ def wait_for_wxo_cpe_health_check(timeout_seconds=45, interval_seconds=2):
39
28
  time.sleep(interval_seconds)
40
29
  return False
41
30
 
42
- def _trim_authorization_urls(env_dict: dict) -> dict:
43
- auth_url_key = "AUTHORIZATION_URL"
44
- env_dict_copy = env_dict.copy()
45
-
46
- auth_url = env_dict_copy.get(auth_url_key)
47
- if not auth_url:
48
- return env_dict_copy
49
-
50
-
51
- parsed_url = urlparse(auth_url)
52
- new_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
53
- env_dict_copy[auth_url_key] = new_url
54
-
55
- return env_dict_copy
56
-
57
-
58
-
59
-
60
31
  def run_compose_lite_cpe(user_env_file: Path) -> bool:
61
- compose_path = get_compose_file()
62
- compose_command = ensure_docker_compose_installed()
63
- _prepare_clean_env(user_env_file)
64
- ensure_docker_installed()
65
-
66
- default_env = read_env_file(get_default_env_file())
67
- user_env = read_env_file(user_env_file) if user_env_file else {}
68
- if not user_env:
69
- user_env = get_persisted_user_env() or {}
70
-
71
- dev_edition_source = get_dev_edition_source(user_env)
72
- default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, user_env, source=dev_edition_source)
73
-
74
- # Update the default environment with the default registry variables only if they are not already set
75
- for key in default_registry_vars:
76
- if key not in default_env or not default_env[key]:
77
- default_env[key] = default_registry_vars[key]
78
-
79
- # Merge the default environment with the user environment
80
- merged_env_dict = {
81
- **default_env,
82
- **user_env,
83
- }
84
-
85
- merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
86
- merged_env_dict = _trim_authorization_urls(merged_env_dict)
32
+ DockerUtils.ensure_docker_installed()
33
+
34
+ cli_config = Config()
35
+ env_service = EnvService(cli_config)
36
+ env_service.prepare_clean_env(user_env_file)
37
+ user_env = env_service.get_user_env(user_env_file)
38
+ merged_env_dict = env_service.prepare_server_env_vars(user_env=user_env, should_drop_auth_routes=True)
87
39
 
88
40
  try:
89
- docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
41
+ DockerLoginService(env_service=env_service).login_by_dev_edition_source(merged_env_dict)
90
42
  except ValueError as ignored:
91
43
  # do nothing, as the docker login here is not mandatory
92
44
  pass
93
45
 
94
- final_env_file = write_merged_env_file(merged_env_dict)
46
+ final_env_file = env_service.write_merged_env_file(merged_env_dict)
95
47
 
96
- command = compose_command + [
97
- "-f", str(compose_path),
98
- "--env-file", str(final_env_file),
99
- "up",
100
- "cpe",
101
- "-d",
102
- "--remove-orphans"
103
- ]
48
+ compose_core = DockerComposeCore(env_service)
104
49
 
105
- logger.info(f"Starting docker-compose Copilot service...")
106
- result = subprocess.run(command, capture_output=False)
50
+ result = compose_core.service_up(service_name="cpe", friendly_name="Copilot", final_env_file=final_env_file)
107
51
 
108
52
  if result.returncode == 0:
109
53
  logger.info("Copilot Service started successfully.")
@@ -124,27 +68,16 @@ def run_compose_lite_cpe(user_env_file: Path) -> bool:
124
68
  return True
125
69
 
126
70
  def run_compose_lite_cpe_down(is_reset: bool = False) -> None:
127
- compose_path = get_compose_file()
128
- compose_command = ensure_docker_compose_installed()
129
- ensure_docker_installed()
130
-
131
- default_env = read_env_file(get_default_env_file())
132
- final_env_file = write_merged_env_file(default_env)
133
-
134
- command = compose_command + [
135
- "-f", str(compose_path),
136
- "--env-file", final_env_file,
137
- "down",
138
- "cpe"
139
- ]
140
-
141
- if is_reset:
142
- command.append("--volumes")
143
- logger.info("Stopping docker-compose Copilot service and resetting volumes...")
144
- else:
145
- logger.info("Stopping docker-compose Copilot service...")
71
+ DockerUtils.ensure_docker_installed()
72
+
73
+ default_env = EnvService.read_env_file(EnvService.get_default_env_file())
74
+ final_env_file = EnvService.write_merged_env_file(default_env)
75
+
76
+ cli_config = Config()
77
+ env_service = EnvService(cli_config)
78
+ compose_core = DockerComposeCore(env_service)
146
79
 
147
- result = subprocess.run(command, capture_output=False)
80
+ result = compose_core.service_down(service_name="cpe", friendly_name="Copilot", final_env_file=final_env_file, is_reset=is_reset)
148
81
 
149
82
  if result.returncode == 0:
150
83
  logger.info("Copilot service stopped successfully.")
@@ -70,7 +70,7 @@ def add_env(
70
70
  ] = None,
71
71
  type: Annotated[
72
72
  EnvironmentAuthType,
73
- typer.Option("--type", "-t", help="The type of auth you wish to use"),
73
+ typer.Option("--type", "-t", help="The type of auth you wish to use. This overrides the auth type that is inferred from the url"),
74
74
  ] = None,
75
75
  insecure: Annotated[
76
76
  bool,
@@ -16,7 +16,7 @@ from typing import Optional
16
16
  from typing_extensions import Annotated
17
17
 
18
18
  from ibm_watsonx_orchestrate import __version__
19
- from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_controller import EvaluationsController
19
+ from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_controller import EvaluationsController, EvaluateMode
20
20
  from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import AgentsController
21
21
 
22
22
  logger = logging.getLogger(__name__)
@@ -220,6 +220,13 @@ def analyze(data_path: Annotated[
220
220
  help="Path to the directory that has the saved results"
221
221
  )
222
222
  ],
223
+ tool_definition_path: Annotated[
224
+ Optional[str],
225
+ typer.Option(
226
+ "--tools-path", "-t",
227
+ help="Path to the directory containing tool definitions."
228
+ )
229
+ ] = None,
223
230
  user_env_file: Annotated[
224
231
  Optional[str],
225
232
  typer.Option(
@@ -230,7 +237,10 @@ def analyze(data_path: Annotated[
230
237
 
231
238
  validate_watsonx_credentials(user_env_file)
232
239
  controller = EvaluationsController()
233
- controller.analyze(data_path=data_path)
240
+ controller.analyze(
241
+ data_path=data_path,
242
+ tool_definition_path=tool_definition_path
243
+ )
234
244
 
235
245
  @evaluation_app.command(name="validate-external", help="Validate an external agent against a set of inputs")
236
246
  def validate_external(
@@ -375,3 +385,214 @@ def validate_external(
375
385
  msg = f"[dark_orange]Schema validation did not succeed. See '{str(validation_folder)}' for failures.[/dark_orange]"
376
386
 
377
387
  rich.print(Panel(msg))
388
+
389
+ @evaluation_app.command(name="validate-native", help="Validate native agents against a set of inputs")
390
+ def validate_native(
391
+ data_path: Annotated[
392
+ str,
393
+ typer.Option(
394
+ "--tsv", "-t",
395
+ help="Path to .tsv file of inputs. The first column of the TSV is the user story, the second column is the expected final output from the agent, and the third column is the name of the agent"
396
+ )
397
+ ],
398
+ output_dir: Annotated[
399
+ str,
400
+ typer.Option(
401
+ "--output", "-o",
402
+ help="where to save the validation results"
403
+ )
404
+ ] = "./test_native_agent",
405
+ user_env_file: Annotated[
406
+ Optional[str],
407
+ typer.Option(
408
+ "--env-file", "-e",
409
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
410
+ ),
411
+ ] = None,
412
+ ):
413
+ validate_watsonx_credentials(user_env_file)
414
+
415
+ eval_dir = os.path.join(output_dir, "native_agent_evaluations")
416
+ if os.path.exists(eval_dir):
417
+ rich.print(f"[yellow]: found existing {eval_dir} in target directory. All content is removed.")
418
+ shutil.rmtree(eval_dir)
419
+ Path(eval_dir).mkdir(exist_ok=True, parents=True)
420
+
421
+ test_data_path = Path(eval_dir) / "generated_test_data"
422
+ test_data_path.mkdir(exist_ok=True, parents=True)
423
+
424
+ controller = EvaluationsController()
425
+ test_data = read_csv(data_path) # tab seperated file containing the user story, the final outcome, and the agent name
426
+
427
+ for idx, row in enumerate(test_data):
428
+ agent_name = row[2] # agent name
429
+ dataset = [row[0:2]] # user story, expected final outcome
430
+ generated_test_data = controller.generate_performance_test(agent_name=agent_name, test_data=dataset)
431
+ for test_data in generated_test_data:
432
+ test_name = f"native_agent_evaluation_test_{idx}.json"
433
+ with open(test_data_path / test_name, encoding="utf-8", mode="w+") as f:
434
+ json.dump(test_data, f, indent=4)
435
+
436
+ evaluate(output_dir=eval_dir, test_paths=str(test_data_path))
437
+
438
+ @evaluation_app.command(name="quick-eval",
439
+ short_help="Evaluate agent against a suite of static metrics and LLM-as-a-judge metrics",
440
+ help="""
441
+ Use the quick-eval command to evaluate your agent against a suite of static metrics and LLM-as-a-judge metrics.
442
+ """)
443
+ def quick_eval(
444
+ config_file: Annotated[
445
+ Optional[str],
446
+ typer.Option(
447
+ "--config", "-c",
448
+ help="Path to YAML configuration file containing evaluation settings."
449
+ )
450
+ ] = None,
451
+ test_paths: Annotated[
452
+ Optional[str],
453
+ typer.Option(
454
+ "--test-paths", "-p",
455
+ help="Paths to the test files and/or directories to evaluate, separated by commas."
456
+ ),
457
+ ] = None,
458
+ tools_path: Annotated[
459
+ str,
460
+ typer.Option(
461
+ "--tools-path", "-t",
462
+ help="Path to the directory containing tool definitions."
463
+ )
464
+ ] = None,
465
+ output_dir: Annotated[
466
+ Optional[str],
467
+ typer.Option(
468
+ "--output-dir", "-o",
469
+ help="Directory to save the evaluation results."
470
+ )
471
+ ] = None,
472
+ user_env_file: Annotated[
473
+ Optional[str],
474
+ typer.Option(
475
+ "--env-file", "-e",
476
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
477
+ ),
478
+ ] = None
479
+ ):
480
+ if not config_file:
481
+ if not test_paths or not output_dir:
482
+ logger.error("Error: Both --test-paths and --output-dir must be provided when not using a config file")
483
+ exit(1)
484
+
485
+ validate_watsonx_credentials(user_env_file)
486
+
487
+ if tools_path is None:
488
+ logger.error("When running `quick-eval`, please provide the path to your tools file.")
489
+ sys.exit(1)
490
+
491
+ controller = EvaluationsController()
492
+ controller.evaluate(
493
+ config_file=config_file,
494
+ test_paths=test_paths,
495
+ output_dir=output_dir,
496
+ tools_path=tools_path, mode=EvaluateMode.referenceless
497
+ )
498
+
499
+
500
+ red_teaming_app = typer.Typer(no_args_is_help=True)
501
+ evaluation_app.add_typer(red_teaming_app, name="red-teaming")
502
+
503
+
504
+ @red_teaming_app.command("list", help="List available red-teaming attack plans")
505
+ def list_plans():
506
+ controller = EvaluationsController()
507
+ controller.list_red_teaming_attacks()
508
+
509
+
510
+ @red_teaming_app.command("plan", help="Generate red-teaming attacks")
511
+ def plan(
512
+ attacks_list: Annotated[
513
+ str,
514
+ typer.Option(
515
+ "--attacks-list",
516
+ "-a",
517
+ help="Comma-separated list of red-teaming attacks to generate.",
518
+ ),
519
+ ],
520
+ datasets_path: Annotated[
521
+ str,
522
+ typer.Option(
523
+ "--datasets-path",
524
+ "-d",
525
+ help="Path to datasets for red-teaming. This can also be a comma-separated list of files or directories.",
526
+ ),
527
+ ],
528
+ agents_path: Annotated[
529
+ str, typer.Option("--agents-path", "-g", help="Path to the directory containing all agent definitions.")
530
+ ],
531
+ target_agent_name: Annotated[
532
+ str,
533
+ typer.Option(
534
+ "--target-agent-name",
535
+ "-t",
536
+ help="Name of the target agent to attack (should be present in agents-path).",
537
+ ),
538
+ ],
539
+ output_dir: Annotated[
540
+ Optional[str],
541
+ typer.Option("--output-dir", "-o", help="Directory to save generated attacks.")
542
+ ]=None,
543
+ user_env_file: Annotated[
544
+ Optional[str],
545
+ typer.Option(
546
+ "--env-file",
547
+ "-e",
548
+ help="Path to a .env file that overrides default.env. Then environment variables override both.",
549
+ ),
550
+ ] = None,
551
+ max_variants: Annotated[
552
+ Optional[int],
553
+ typer.Option(
554
+ "--max_variants",
555
+ "-n",
556
+ help="Number of variants to generate per attack type.",
557
+ ),
558
+ ] = None,
559
+
560
+ ):
561
+ validate_watsonx_credentials(user_env_file)
562
+ controller = EvaluationsController()
563
+ controller.generate_red_teaming_attacks(
564
+ attacks_list=attacks_list,
565
+ datasets_path=datasets_path,
566
+ agents_path=agents_path,
567
+ target_agent_name=target_agent_name,
568
+ output_dir=output_dir,
569
+ max_variants=max_variants,
570
+ )
571
+
572
+
573
+ @red_teaming_app.command("run", help="Run red-teaming attacks")
574
+ def run(
575
+ attack_paths: Annotated[
576
+ str,
577
+ typer.Option(
578
+ "--attack-paths",
579
+ "-a",
580
+ help="Path or list of paths (comma-separated) to directories containing attack files.",
581
+ ),
582
+ ],
583
+ output_dir: Annotated[
584
+ Optional[str],
585
+ typer.Option("--output-dir", "-o", help="Directory to save attack results."),
586
+ ] = None,
587
+ user_env_file: Annotated[
588
+ Optional[str],
589
+ typer.Option(
590
+ "--env-file",
591
+ "-e",
592
+ help="Path to a .env file that overrides default.env. Then environment variables override both.",
593
+ ),
594
+ ] = None,
595
+ ):
596
+ validate_watsonx_credentials(user_env_file)
597
+ controller = EvaluationsController()
598
+ controller.run_red_teaming_attacks(attack_paths=attack_paths, output_dir=output_dir)