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.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +30 -0
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +8 -5
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +25 -10
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +124 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +3 -3
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +20 -2
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +10 -2
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +421 -177
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +18 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +114 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +24 -91
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +223 -2
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +93 -9
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +3 -3
- ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_command.py +56 -0
- ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +458 -0
- ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +107 -0
- ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +12 -0
- ibm_watsonx_orchestrate/cli/commands/partners/partners_controller.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +114 -635
- ibm_watsonx_orchestrate/cli/commands/server/types.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -3
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +206 -43
- ibm_watsonx_orchestrate/cli/main.py +2 -0
- ibm_watsonx_orchestrate/client/base_api_client.py +31 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +18 -1
- ibm_watsonx_orchestrate/client/service_instance.py +19 -34
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +3 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +5 -2
- ibm_watsonx_orchestrate/client/utils.py +34 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +14 -12
- ibm_watsonx_orchestrate/docker/default.env +17 -17
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +3 -1
- ibm_watsonx_orchestrate/flow_builder/types.py +252 -1
- ibm_watsonx_orchestrate/utils/docker_utils.py +280 -0
- ibm_watsonx_orchestrate/utils/environment.py +369 -0
- ibm_watsonx_orchestrate/utils/utils.py +1 -1
- {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/RECORD +47 -39
- {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.12.0b0.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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 =
|
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(
|
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)
|