ibm-watsonx-orchestrate 1.11.1__py3-none-any.whl → 1.12.0__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 +22 -5
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +3 -3
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -0
- ibm_watsonx_orchestrate/agent_builder/models/types.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +184 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +9 -3
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +20 -2
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +19 -6
- 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_controller.py +2 -6
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +24 -91
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +52 -2
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +23 -4
- 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 +475 -0
- ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +99 -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 +124 -637
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +2 -2
- 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 +233 -44
- ibm_watsonx_orchestrate/cli/main.py +2 -0
- ibm_watsonx_orchestrate/client/connections/connections_client.py +4 -1
- 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 +31 -1
- ibm_watsonx_orchestrate/docker/compose-lite.yml +58 -7
- ibm_watsonx_orchestrate/docker/default.env +20 -17
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +10 -2
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +71 -9
- ibm_watsonx_orchestrate/flow_builder/node.py +14 -2
- ibm_watsonx_orchestrate/flow_builder/types.py +36 -3
- ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
- ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
- ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
- ibm_watsonx_orchestrate/utils/docker_utils.py +280 -0
- ibm_watsonx_orchestrate/utils/environment.py +369 -0
- ibm_watsonx_orchestrate/utils/utils.py +7 -3
- {ibm_watsonx_orchestrate-1.11.1.dist-info → ibm_watsonx_orchestrate-1.12.0.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.11.1.dist-info → ibm_watsonx_orchestrate-1.12.0.dist-info}/RECORD +52 -41
- {ibm_watsonx_orchestrate-1.11.1.dist-info → ibm_watsonx_orchestrate-1.12.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.11.1.dist-info → ibm_watsonx_orchestrate-1.12.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.11.1.dist-info → ibm_watsonx_orchestrate-1.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -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, allow_unicode=True)
|
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, allow_unicode=True)
|
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'.")
|
@@ -327,11 +327,7 @@ def talk_to_cpe(cpe_client, samples_file=None, context_data=None):
|
|
327
327
|
|
328
328
|
|
329
329
|
def prompt_tune(agent_spec: str, output_file: str | None, samples_file: str | None, dry_run_flag: bool) -> None:
|
330
|
-
|
331
|
-
if not agents:
|
332
|
-
logger.error("Invalid agent spec file provided, no agent found.")
|
333
|
-
sys.exit(1)
|
334
|
-
agent = agents[0]
|
330
|
+
agent = AgentsController.import_agent(file=agent_spec, app_id=None)[0]
|
335
331
|
agent_kind = agent.kind
|
336
332
|
|
337
333
|
if agent_kind != AgentKind.NATIVE:
|
@@ -346,7 +342,7 @@ def prompt_tune(agent_spec: str, output_file: str | None, samples_file: str | No
|
|
346
342
|
|
347
343
|
client = get_cpe_client()
|
348
344
|
|
349
|
-
instr = agent.instructions
|
345
|
+
instr = agent.instructions
|
350
346
|
|
351
347
|
tools = _get_tools_from_names(agent.tools)
|
352
348
|
|
@@ -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.")
|
@@ -54,7 +54,8 @@ def read_env_file(env_path: Path|str) -> dict:
|
|
54
54
|
def validate_watsonx_credentials(user_env_file: str) -> bool:
|
55
55
|
required_sets = [
|
56
56
|
["WATSONX_SPACE_ID", "WATSONX_APIKEY"],
|
57
|
-
["WO_INSTANCE", "WO_API_KEY"]
|
57
|
+
["WO_INSTANCE", "WO_API_KEY"],
|
58
|
+
["WO_INSTANCE", "WO_PASSWORD", "WO_USERNAME"]
|
58
59
|
]
|
59
60
|
|
60
61
|
def has_valid_keys(env: dict) -> bool:
|
@@ -75,7 +76,7 @@ def validate_watsonx_credentials(user_env_file: str) -> bool:
|
|
75
76
|
user_env = read_env_file(user_env_file)
|
76
77
|
|
77
78
|
if not has_valid_keys(user_env):
|
78
|
-
logger.error("Error: The environment file does not contain the required keys: either WATSONX_SPACE_ID and WATSONX_APIKEY or WO_INSTANCE and WO_API_KEY.")
|
79
|
+
logger.error("Error: The environment file does not contain the required keys: either WATSONX_SPACE_ID and WATSONX_APIKEY or WO_INSTANCE and WO_API_KEY or WO_INSTANCE and WO_USERNAME and WO_PASSWORD.")
|
79
80
|
sys.exit(1)
|
80
81
|
|
81
82
|
# Update os.environ with whichever set is present
|
@@ -386,6 +387,55 @@ def validate_external(
|
|
386
387
|
|
387
388
|
rich.print(Panel(msg))
|
388
389
|
|
390
|
+
@evaluation_app.command(name="validate-native", help="Validate native agents against a set of inputs")
|
391
|
+
def validate_native(
|
392
|
+
data_path: Annotated[
|
393
|
+
str,
|
394
|
+
typer.Option(
|
395
|
+
"--tsv", "-t",
|
396
|
+
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"
|
397
|
+
)
|
398
|
+
],
|
399
|
+
output_dir: Annotated[
|
400
|
+
str,
|
401
|
+
typer.Option(
|
402
|
+
"--output", "-o",
|
403
|
+
help="where to save the validation results"
|
404
|
+
)
|
405
|
+
] = "./test_native_agent",
|
406
|
+
user_env_file: Annotated[
|
407
|
+
Optional[str],
|
408
|
+
typer.Option(
|
409
|
+
"--env-file", "-e",
|
410
|
+
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
411
|
+
),
|
412
|
+
] = None,
|
413
|
+
):
|
414
|
+
validate_watsonx_credentials(user_env_file)
|
415
|
+
|
416
|
+
eval_dir = os.path.join(output_dir, "native_agent_evaluations")
|
417
|
+
if os.path.exists(eval_dir):
|
418
|
+
rich.print(f"[yellow]: found existing {eval_dir} in target directory. All content is removed.")
|
419
|
+
shutil.rmtree(eval_dir)
|
420
|
+
Path(eval_dir).mkdir(exist_ok=True, parents=True)
|
421
|
+
|
422
|
+
test_data_path = Path(eval_dir) / "generated_test_data"
|
423
|
+
test_data_path.mkdir(exist_ok=True, parents=True)
|
424
|
+
|
425
|
+
controller = EvaluationsController()
|
426
|
+
test_data = read_csv(data_path) # tab seperated file containing the user story, the final outcome, and the agent name
|
427
|
+
|
428
|
+
for idx, row in enumerate(test_data):
|
429
|
+
agent_name = row[2] # agent name
|
430
|
+
dataset = [row[0:2]] # user story, expected final outcome
|
431
|
+
generated_test_data = controller.generate_performance_test(agent_name=agent_name, test_data=dataset)
|
432
|
+
for test_data in generated_test_data:
|
433
|
+
test_name = f"native_agent_evaluation_test_{idx}.json"
|
434
|
+
with open(test_data_path / test_name, encoding="utf-8", mode="w+") as f:
|
435
|
+
json.dump(test_data, f, indent=4)
|
436
|
+
|
437
|
+
evaluate(output_dir=eval_dir, test_paths=str(test_data_path))
|
438
|
+
|
389
439
|
@evaluation_app.command(name="quick-eval",
|
390
440
|
short_help="Evaluate agent against a suite of static metrics and LLM-as-a-judge metrics",
|
391
441
|
help="""
|
@@ -52,7 +52,7 @@ class EvaluationsController:
|
|
52
52
|
|
53
53
|
if "WATSONX_SPACE_ID" in os.environ and "WATSONX_APIKEY" in os.environ:
|
54
54
|
provider = "watsonx"
|
55
|
-
elif "WO_INSTANCE" in os.environ and "WO_API_KEY" in os.environ:
|
55
|
+
elif "WO_INSTANCE" in os.environ and ("WO_API_KEY" in os.environ or "WO_PASSWORD" in os.environ):
|
56
56
|
provider = "model_proxy"
|
57
57
|
else:
|
58
58
|
logger.error(
|
@@ -101,10 +101,11 @@ PROVIDER_EXTRA_PROPERTIES_LUT = {
|
|
101
101
|
PROVIDER_REQUIRED_FIELDS = {k:['api_key'] for k in ModelProvider}
|
102
102
|
# Update required fields for each provider
|
103
103
|
# Use sets to denote when a requirement is 'or'
|
104
|
+
# Tuples denote combined requirements like 'and'
|
104
105
|
PROVIDER_REQUIRED_FIELDS.update({
|
105
106
|
ModelProvider.WATSONX: PROVIDER_REQUIRED_FIELDS[ModelProvider.WATSONX] + [{'watsonx_space_id', 'watsonx_project_id', 'watsonx_deployment_id'}],
|
106
107
|
ModelProvider.OLLAMA: PROVIDER_REQUIRED_FIELDS[ModelProvider.OLLAMA] + ['custom_host'],
|
107
|
-
ModelProvider.BEDROCK: [],
|
108
|
+
ModelProvider.BEDROCK: [{'api_key', ('aws_secret_access_key', 'aws_access_key_id')}],
|
108
109
|
})
|
109
110
|
|
110
111
|
# def env_file_to_model_ProviderConfig(model_name: str, env_file_path: str) -> ProviderConfig | None:
|
@@ -158,16 +159,34 @@ def _validate_extra_fields(provider: ModelProvider, cfg: ProviderConfig) -> None
|
|
158
159
|
if cfg.__dict__.get(attr) is not None and attr not in accepted_fields:
|
159
160
|
logger.warning(f"The config option '{attr}' is not used by provider '{provider}'")
|
160
161
|
|
162
|
+
def _check_credential_provided(cred: str | tuple, provided_creds: set) -> bool:
|
163
|
+
if isinstance(cred, tuple):
|
164
|
+
return all(item in provided_creds for item in cred)
|
165
|
+
else:
|
166
|
+
return cred in provided_creds
|
167
|
+
|
168
|
+
def _format_missing_credential(missing_credential: set) -> str:
|
169
|
+
parts = []
|
170
|
+
for cred in missing_credential:
|
171
|
+
if isinstance(cred, tuple):
|
172
|
+
formatted = " and ".join(f"{x}" for x in cred)
|
173
|
+
parts.append(f"({formatted})")
|
174
|
+
else:
|
175
|
+
parts.append(f"{cred}")
|
176
|
+
|
177
|
+
return " or ".join(parts)
|
178
|
+
|
179
|
+
|
161
180
|
def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id: str = None) -> None:
|
162
181
|
provided_credentials = set([k for k,v in dict(cfg).items() if v is not None])
|
163
182
|
required_creds = PROVIDER_REQUIRED_FIELDS[provider]
|
164
183
|
missing_credentials = []
|
165
184
|
for cred in required_creds:
|
166
185
|
if isinstance(cred, set):
|
167
|
-
if not any(c
|
186
|
+
if not any(_check_credential_provided(c, provided_credentials) for c in cred):
|
168
187
|
missing_credentials.append(cred)
|
169
188
|
else:
|
170
|
-
if
|
189
|
+
if not _check_credential_provided(cred, provided_credentials):
|
171
190
|
missing_credentials.append(cred)
|
172
191
|
|
173
192
|
if len(missing_credentials) > 0:
|
@@ -177,7 +196,7 @@ def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id:
|
|
177
196
|
missing_credentials_string = f"Be sure to include the following required fields for provider '{provider}' in the connection '{app_id}':"
|
178
197
|
for cred in missing_credentials:
|
179
198
|
if isinstance(cred, set):
|
180
|
-
cred_str =
|
199
|
+
cred_str = _format_missing_credential(cred)
|
181
200
|
else:
|
182
201
|
cred_str = cred
|
183
202
|
missing_credentials_string += f"\n\t - {cred_str}"
|
@@ -12,7 +12,6 @@ import requests
|
|
12
12
|
import rich
|
13
13
|
import rich.highlighter
|
14
14
|
|
15
|
-
from ibm_watsonx_orchestrate.cli.commands.server.server_command import get_default_env_file, merge_env
|
16
15
|
from ibm_watsonx_orchestrate.client.model_policies.model_policies_client import ModelPoliciesClient
|
17
16
|
from ibm_watsonx_orchestrate.agent_builder.model_policies.types import ModelPolicy, ModelPolicyInner, \
|
18
17
|
ModelPolicyRetry, ModelPolicyStrategy, ModelPolicyStrategyMode, ModelPolicyTarget
|
@@ -20,6 +19,7 @@ from ibm_watsonx_orchestrate.client.models.models_client import ModelsClient
|
|
20
19
|
from ibm_watsonx_orchestrate.agent_builder.models.types import VirtualModel, ProviderConfig, ModelType, ANTHROPIC_DEFAULT_MAX_TOKENS
|
21
20
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_cpd_env
|
22
21
|
from ibm_watsonx_orchestrate.client.connections import get_connection_id, ConnectionType
|
22
|
+
from ibm_watsonx_orchestrate.utils.environment import EnvService
|
23
23
|
|
24
24
|
logger = logging.getLogger(__name__)
|
25
25
|
|
@@ -153,8 +153,8 @@ class ModelsController:
|
|
153
153
|
models_client: ModelsClient = self.get_models_client()
|
154
154
|
model_policies_client: ModelPoliciesClient = self.get_model_policies_client()
|
155
155
|
global WATSONX_URL
|
156
|
-
default_env_path = get_default_env_file()
|
157
|
-
merged_env_dict = merge_env(
|
156
|
+
default_env_path = EnvService.get_default_env_file()
|
157
|
+
merged_env_dict = EnvService.merge_env(
|
158
158
|
default_env_path,
|
159
159
|
None
|
160
160
|
)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import typer
|
2
|
+
from typing_extensions import Annotated
|
3
|
+
from ibm_watsonx_orchestrate.cli.commands.partners.offering.partners_offering_controller import PartnersOfferingController
|
4
|
+
from typing_extensions import Annotated
|
5
|
+
from ibm_watsonx_orchestrate.cli.commands.partners.offering.types import AgentKind
|
6
|
+
|
7
|
+
|
8
|
+
partners_offering = typer.Typer(no_args_is_help=True)
|
9
|
+
|
10
|
+
@partners_offering.command(
|
11
|
+
name="create",
|
12
|
+
help="Export Items from your environment to create an offering"
|
13
|
+
)
|
14
|
+
def create_offering(
|
15
|
+
offering: Annotated[
|
16
|
+
str,
|
17
|
+
typer.Option("--offering", "-o", help="Name of the offering"),
|
18
|
+
],
|
19
|
+
publisher_name: Annotated[
|
20
|
+
str,
|
21
|
+
typer.Option("--publisher", "-p", help="Publisher name"),
|
22
|
+
],
|
23
|
+
type: Annotated[
|
24
|
+
AgentKind,
|
25
|
+
typer.Option("--type", "-t", help="Type of agent: native|external"),
|
26
|
+
],
|
27
|
+
agent_name: Annotated[
|
28
|
+
str,
|
29
|
+
typer.Option("--agent-name", "-a", help="Agent name to create"),
|
30
|
+
],
|
31
|
+
):
|
32
|
+
controller = PartnersOfferingController()
|
33
|
+
controller.create(
|
34
|
+
offering=offering,
|
35
|
+
publisher_name=publisher_name,
|
36
|
+
agent_type=type,
|
37
|
+
agent_name=agent_name,
|
38
|
+
)
|
39
|
+
|
40
|
+
|
41
|
+
@partners_offering.command(
|
42
|
+
name="package",
|
43
|
+
help="Validate your exported offering and package for upload"
|
44
|
+
)
|
45
|
+
def package_offering(
|
46
|
+
offering: Annotated[
|
47
|
+
str,
|
48
|
+
typer.Option("--offering", "-o", help="Name of the offering to package"),
|
49
|
+
],
|
50
|
+
folder_path: Annotated[
|
51
|
+
str,
|
52
|
+
typer.Option("--folder", "-f", help="Path to folder containing the specified offering")
|
53
|
+
] = None
|
54
|
+
):
|
55
|
+
controller = PartnersOfferingController()
|
56
|
+
controller.package(offering=offering, folder_path=folder_path)
|