ibm-watsonx-orchestrate 1.10.1__py3-none-any.whl → 1.11.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 +13 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +25 -10
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +10 -2
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +404 -173
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +6 -2
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +174 -2
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +93 -9
- ibm_watsonx_orchestrate/cli/commands/server/types.py +1 -1
- ibm_watsonx_orchestrate/client/base_api_client.py +31 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +14 -0
- ibm_watsonx_orchestrate/client/service_instance.py +19 -34
- ibm_watsonx_orchestrate/client/utils.py +3 -1
- ibm_watsonx_orchestrate/docker/compose-lite.yml +12 -11
- ibm_watsonx_orchestrate/docker/default.env +13 -13
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +3 -1
- ibm_watsonx_orchestrate/flow_builder/types.py +252 -1
- {ibm_watsonx_orchestrate-1.10.1.dist-info → ibm_watsonx_orchestrate-1.11.0.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.10.1.dist-info → ibm_watsonx_orchestrate-1.11.0.dist-info}/RECORD +23 -23
- {ibm_watsonx_orchestrate-1.10.1.dist-info → ibm_watsonx_orchestrate-1.11.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.10.1.dist-info → ibm_watsonx_orchestrate-1.11.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.10.1.dist-info → ibm_watsonx_orchestrate-1.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -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,165 @@ 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="quick-eval",
|
390
|
+
short_help="Evaluate agent against a suite of static metrics and LLM-as-a-judge metrics",
|
391
|
+
help="""
|
392
|
+
Use the quick-eval command to evaluate your agent against a suite of static metrics and LLM-as-a-judge metrics.
|
393
|
+
""")
|
394
|
+
def quick_eval(
|
395
|
+
config_file: Annotated[
|
396
|
+
Optional[str],
|
397
|
+
typer.Option(
|
398
|
+
"--config", "-c",
|
399
|
+
help="Path to YAML configuration file containing evaluation settings."
|
400
|
+
)
|
401
|
+
] = None,
|
402
|
+
test_paths: Annotated[
|
403
|
+
Optional[str],
|
404
|
+
typer.Option(
|
405
|
+
"--test-paths", "-p",
|
406
|
+
help="Paths to the test files and/or directories to evaluate, separated by commas."
|
407
|
+
),
|
408
|
+
] = None,
|
409
|
+
tools_path: Annotated[
|
410
|
+
str,
|
411
|
+
typer.Option(
|
412
|
+
"--tools-path", "-t",
|
413
|
+
help="Path to the directory containing tool definitions."
|
414
|
+
)
|
415
|
+
] = None,
|
416
|
+
output_dir: Annotated[
|
417
|
+
Optional[str],
|
418
|
+
typer.Option(
|
419
|
+
"--output-dir", "-o",
|
420
|
+
help="Directory to save the evaluation results."
|
421
|
+
)
|
422
|
+
] = None,
|
423
|
+
user_env_file: Annotated[
|
424
|
+
Optional[str],
|
425
|
+
typer.Option(
|
426
|
+
"--env-file", "-e",
|
427
|
+
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
428
|
+
),
|
429
|
+
] = None
|
430
|
+
):
|
431
|
+
if not config_file:
|
432
|
+
if not test_paths or not output_dir:
|
433
|
+
logger.error("Error: Both --test-paths and --output-dir must be provided when not using a config file")
|
434
|
+
exit(1)
|
435
|
+
|
436
|
+
validate_watsonx_credentials(user_env_file)
|
437
|
+
|
438
|
+
if tools_path is None:
|
439
|
+
logger.error("When running `quick-eval`, please provide the path to your tools file.")
|
440
|
+
sys.exit(1)
|
441
|
+
|
442
|
+
controller = EvaluationsController()
|
443
|
+
controller.evaluate(
|
444
|
+
config_file=config_file,
|
445
|
+
test_paths=test_paths,
|
446
|
+
output_dir=output_dir,
|
447
|
+
tools_path=tools_path, mode=EvaluateMode.referenceless
|
448
|
+
)
|
449
|
+
|
450
|
+
|
451
|
+
red_teaming_app = typer.Typer(no_args_is_help=True)
|
452
|
+
evaluation_app.add_typer(red_teaming_app, name="red-teaming")
|
453
|
+
|
454
|
+
|
455
|
+
@red_teaming_app.command("list", help="List available red-teaming attack plans")
|
456
|
+
def list_plans():
|
457
|
+
controller = EvaluationsController()
|
458
|
+
controller.list_red_teaming_attacks()
|
459
|
+
|
460
|
+
|
461
|
+
@red_teaming_app.command("plan", help="Generate red-teaming attacks")
|
462
|
+
def plan(
|
463
|
+
attacks_list: Annotated[
|
464
|
+
str,
|
465
|
+
typer.Option(
|
466
|
+
"--attacks-list",
|
467
|
+
"-a",
|
468
|
+
help="Comma-separated list of red-teaming attacks to generate.",
|
469
|
+
),
|
470
|
+
],
|
471
|
+
datasets_path: Annotated[
|
472
|
+
str,
|
473
|
+
typer.Option(
|
474
|
+
"--datasets-path",
|
475
|
+
"-d",
|
476
|
+
help="Path to datasets for red-teaming. This can also be a comma-separated list of files or directories.",
|
477
|
+
),
|
478
|
+
],
|
479
|
+
agents_path: Annotated[
|
480
|
+
str, typer.Option("--agents-path", "-g", help="Path to the directory containing all agent definitions.")
|
481
|
+
],
|
482
|
+
target_agent_name: Annotated[
|
483
|
+
str,
|
484
|
+
typer.Option(
|
485
|
+
"--target-agent-name",
|
486
|
+
"-t",
|
487
|
+
help="Name of the target agent to attack (should be present in agents-path).",
|
488
|
+
),
|
489
|
+
],
|
490
|
+
output_dir: Annotated[
|
491
|
+
Optional[str],
|
492
|
+
typer.Option("--output-dir", "-o", help="Directory to save generated attacks.")
|
493
|
+
]=None,
|
494
|
+
user_env_file: Annotated[
|
495
|
+
Optional[str],
|
496
|
+
typer.Option(
|
497
|
+
"--env-file",
|
498
|
+
"-e",
|
499
|
+
help="Path to a .env file that overrides default.env. Then environment variables override both.",
|
500
|
+
),
|
501
|
+
] = None,
|
502
|
+
max_variants: Annotated[
|
503
|
+
Optional[int],
|
504
|
+
typer.Option(
|
505
|
+
"--max_variants",
|
506
|
+
"-n",
|
507
|
+
help="Number of variants to generate per attack type.",
|
508
|
+
),
|
509
|
+
] = None,
|
510
|
+
|
511
|
+
):
|
512
|
+
validate_watsonx_credentials(user_env_file)
|
513
|
+
controller = EvaluationsController()
|
514
|
+
controller.generate_red_teaming_attacks(
|
515
|
+
attacks_list=attacks_list,
|
516
|
+
datasets_path=datasets_path,
|
517
|
+
agents_path=agents_path,
|
518
|
+
target_agent_name=target_agent_name,
|
519
|
+
output_dir=output_dir,
|
520
|
+
max_variants=max_variants,
|
521
|
+
)
|
522
|
+
|
523
|
+
|
524
|
+
@red_teaming_app.command("run", help="Run red-teaming attacks")
|
525
|
+
def run(
|
526
|
+
attack_paths: Annotated[
|
527
|
+
str,
|
528
|
+
typer.Option(
|
529
|
+
"--attack-paths",
|
530
|
+
"-a",
|
531
|
+
help="Path or list of paths (comma-separated) to directories containing attack files.",
|
532
|
+
),
|
533
|
+
],
|
534
|
+
output_dir: Annotated[
|
535
|
+
Optional[str],
|
536
|
+
typer.Option("--output-dir", "-o", help="Directory to save attack results."),
|
537
|
+
] = None,
|
538
|
+
user_env_file: Annotated[
|
539
|
+
Optional[str],
|
540
|
+
typer.Option(
|
541
|
+
"--env-file",
|
542
|
+
"-e",
|
543
|
+
help="Path to a .env file that overrides default.env. Then environment variables override both.",
|
544
|
+
),
|
545
|
+
] = None,
|
546
|
+
):
|
547
|
+
validate_watsonx_credentials(user_env_file)
|
548
|
+
controller = EvaluationsController()
|
549
|
+
controller.run_red_teaming_attacks(attack_paths=attack_paths, output_dir=output_dir)
|
@@ -1,17 +1,23 @@
|
|
1
1
|
import logging
|
2
2
|
import os.path
|
3
3
|
from typing import List, Dict, Optional, Tuple
|
4
|
+
from enum import StrEnum
|
4
5
|
import csv
|
5
6
|
from pathlib import Path
|
6
7
|
import sys
|
7
8
|
from wxo_agentic_evaluation import main as evaluate
|
9
|
+
from wxo_agentic_evaluation import quick_eval
|
8
10
|
from wxo_agentic_evaluation.tool_planner import build_snapshot
|
9
|
-
from wxo_agentic_evaluation.analyze_run import
|
11
|
+
from wxo_agentic_evaluation.analyze_run import Analyzer
|
10
12
|
from wxo_agentic_evaluation.batch_annotate import generate_test_cases_from_stories
|
11
|
-
from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig, ProviderConfig
|
13
|
+
from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig, ProviderConfig, AttackConfig, QuickEvalConfig
|
12
14
|
from wxo_agentic_evaluation.record_chat import record_chats
|
13
15
|
from wxo_agentic_evaluation.external_agent.external_validate import ExternalAgentValidation
|
14
16
|
from wxo_agentic_evaluation.external_agent.performance_test import ExternalAgentPerformanceTest
|
17
|
+
from wxo_agentic_evaluation.red_teaming.attack_list import print_attacks
|
18
|
+
from wxo_agentic_evaluation.red_teaming import attack_generator
|
19
|
+
from wxo_agentic_evaluation.red_teaming.attack_runner import run_attacks
|
20
|
+
from wxo_agentic_evaluation.arg_configs import AttackGeneratorConfig
|
15
21
|
from ibm_watsonx_orchestrate import __version__
|
16
22
|
from ibm_watsonx_orchestrate.cli.config import Config, ENV_WXO_URL_OPT, AUTH_CONFIG_FILE, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT
|
17
23
|
from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
|
@@ -21,6 +27,9 @@ import uuid
|
|
21
27
|
|
22
28
|
logger = logging.getLogger(__name__)
|
23
29
|
|
30
|
+
class EvaluateMode(StrEnum):
|
31
|
+
default = "default" # referenceFUL evaluation
|
32
|
+
referenceless = "referenceless"
|
24
33
|
|
25
34
|
class EvaluationsController:
|
26
35
|
def __init__(self):
|
@@ -38,7 +47,7 @@ class EvaluationsController:
|
|
38
47
|
|
39
48
|
return url, tenant_name, token
|
40
49
|
|
41
|
-
def evaluate(self, config_file: Optional[str] = None, test_paths: Optional[str] = None, output_dir: Optional[str] = None) -> None:
|
50
|
+
def evaluate(self, config_file: Optional[str] = None, test_paths: Optional[str] = None, output_dir: Optional[str] = None, tools_path: str = None, mode: str = EvaluateMode.default) -> None:
|
42
51
|
url, tenant_name, token = self._get_env_config()
|
43
52
|
|
44
53
|
if "WATSONX_SPACE_ID" in os.environ and "WATSONX_APIKEY" in os.environ:
|
@@ -90,9 +99,13 @@ class EvaluationsController:
|
|
90
99
|
config_data["output_dir"] = output_dir
|
91
100
|
logger.info(f"Using output directory: {config_data['output_dir']}")
|
92
101
|
|
93
|
-
|
94
|
-
|
95
|
-
|
102
|
+
if mode == EvaluateMode.default:
|
103
|
+
config = TestConfig(**config_data)
|
104
|
+
evaluate.main(config)
|
105
|
+
elif mode == EvaluateMode.referenceless:
|
106
|
+
config_data["tools_path"] = tools_path
|
107
|
+
config = QuickEvalConfig(**config_data)
|
108
|
+
quick_eval.main(config)
|
96
109
|
|
97
110
|
def record(self, output_dir) -> None:
|
98
111
|
|
@@ -160,9 +173,13 @@ class EvaluationsController:
|
|
160
173
|
|
161
174
|
logger.info("Test cases stored at: %s", output_dir)
|
162
175
|
|
163
|
-
def analyze(self, data_path: str) -> None:
|
164
|
-
config = AnalyzeConfig(
|
165
|
-
|
176
|
+
def analyze(self, data_path: str, tool_definition_path: str) -> None:
|
177
|
+
config = AnalyzeConfig(
|
178
|
+
data_path=data_path,
|
179
|
+
tool_definition_path=tool_definition_path
|
180
|
+
)
|
181
|
+
analyzer = Analyzer()
|
182
|
+
analyzer.analyze(config)
|
166
183
|
|
167
184
|
def summarize(self) -> None:
|
168
185
|
pass
|
@@ -187,3 +204,70 @@ class EvaluationsController:
|
|
187
204
|
generated_performance_tests = performance_test.generate_tests()
|
188
205
|
|
189
206
|
return generated_performance_tests
|
207
|
+
|
208
|
+
def list_red_teaming_attacks(self):
|
209
|
+
print_attacks()
|
210
|
+
|
211
|
+
def generate_red_teaming_attacks(
|
212
|
+
self,
|
213
|
+
attacks_list: str,
|
214
|
+
datasets_path: str,
|
215
|
+
agents_path: str,
|
216
|
+
target_agent_name: str,
|
217
|
+
output_dir: Optional[str] = None,
|
218
|
+
max_variants: Optional[int] = None,
|
219
|
+
):
|
220
|
+
if output_dir is None:
|
221
|
+
output_dir = os.path.join(os.getcwd(), "red_teaming_attacks")
|
222
|
+
os.makedirs(output_dir, exist_ok=True)
|
223
|
+
logger.info(f"No output directory specified. Using default: {output_dir}")
|
224
|
+
|
225
|
+
results = attack_generator.main(
|
226
|
+
AttackGeneratorConfig(
|
227
|
+
attacks_list=attacks_list.split(","),
|
228
|
+
datasets_path=datasets_path.split(","),
|
229
|
+
agents_path=agents_path,
|
230
|
+
target_agent_name=target_agent_name,
|
231
|
+
output_dir=output_dir,
|
232
|
+
max_variants=max_variants,
|
233
|
+
)
|
234
|
+
)
|
235
|
+
logger.info(f"Generated {len(results)} attacks and saved to {output_dir}")
|
236
|
+
|
237
|
+
def run_red_teaming_attacks(self, attack_paths: str, output_dir: Optional[str] = None) -> None:
|
238
|
+
url, tenant_name, token = self._get_env_config()
|
239
|
+
|
240
|
+
if "WATSONX_SPACE_ID" in os.environ and "WATSONX_APIKEY" in os.environ:
|
241
|
+
provider = "watsonx"
|
242
|
+
elif "WO_INSTANCE" in os.environ and "WO_API_KEY" in os.environ:
|
243
|
+
provider = "model_proxy"
|
244
|
+
else:
|
245
|
+
logger.error(
|
246
|
+
"No provider found. Please either provide a config_file or set either WATSONX_SPACE_ID and WATSONX_APIKEY or WO_INSTANCE and WO_API_KEY in your system environment variables."
|
247
|
+
)
|
248
|
+
sys.exit(1)
|
249
|
+
|
250
|
+
config_data = {
|
251
|
+
"auth_config": AuthConfig(
|
252
|
+
url=url,
|
253
|
+
tenant_name=tenant_name,
|
254
|
+
token=token,
|
255
|
+
),
|
256
|
+
"provider_config": ProviderConfig(
|
257
|
+
provider=provider,
|
258
|
+
model_id="meta-llama/llama-3-405b-instruct",
|
259
|
+
),
|
260
|
+
}
|
261
|
+
|
262
|
+
config_data["attack_paths"] = attack_paths.split(",")
|
263
|
+
if output_dir:
|
264
|
+
config_data["output_dir"] = output_dir
|
265
|
+
else:
|
266
|
+
config_data["output_dir"] = os.path.join(os.getcwd(), "red_teaming_results")
|
267
|
+
os.makedirs(config_data["output_dir"], exist_ok=True)
|
268
|
+
logger.info(f"No output directory specified. Using default: {config_data['output_dir']}")
|
269
|
+
|
270
|
+
|
271
|
+
config = AttackConfig(**config_data)
|
272
|
+
|
273
|
+
run_attacks(config)
|
@@ -92,7 +92,7 @@ class ModelGatewayEnvConfig(BaseModel):
|
|
92
92
|
inferred_auth_url = config.get("WO_INSTANCE") + '/icp4d-api/v1/authorize'
|
93
93
|
else:
|
94
94
|
logger.error(f"No 'AUTHORIZATION_URL' found. Auth type '{auth_type}' does not support defaulting. Please set the 'AUTHORIZATION_URL' explictly")
|
95
|
-
|
95
|
+
sys.exit(1)
|
96
96
|
config["AUTHORIZATION_URL"] = inferred_auth_url
|
97
97
|
|
98
98
|
if auth_type != WoAuthType.CPD:
|
@@ -1,9 +1,22 @@
|
|
1
1
|
import json
|
2
|
-
|
2
|
+
from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
|
3
3
|
import requests
|
4
4
|
from abc import ABC, abstractmethod
|
5
5
|
from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
|
6
6
|
from typing_extensions import List
|
7
|
+
from contextlib import contextmanager
|
8
|
+
|
9
|
+
@contextmanager
|
10
|
+
def ssl_handler():
|
11
|
+
try:
|
12
|
+
yield
|
13
|
+
except requests.exceptions.SSLError as e:
|
14
|
+
error_message = str(e)
|
15
|
+
if "self-signed certificate in certificate chain" in error_message:
|
16
|
+
reason = "[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain"
|
17
|
+
else:
|
18
|
+
reason = error_message
|
19
|
+
raise BadRequest(f"SSL handshake failed for request '{e.request.path_url}'. Reason: '{reason}'")
|
7
20
|
|
8
21
|
|
9
22
|
class ClientAPIException(requests.HTTPError):
|
@@ -50,7 +63,8 @@ class BaseAPIClient:
|
|
50
63
|
|
51
64
|
def _get(self, path: str, params: dict = None, data=None, return_raw=False) -> dict:
|
52
65
|
url = f"{self.base_url}{path}"
|
53
|
-
|
66
|
+
with ssl_handler():
|
67
|
+
response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
|
54
68
|
self._check_response(response)
|
55
69
|
if not return_raw:
|
56
70
|
return response.json()
|
@@ -59,13 +73,15 @@ class BaseAPIClient:
|
|
59
73
|
|
60
74
|
def _post(self, path: str, data: dict = None, files: dict = None) -> dict:
|
61
75
|
url = f"{self.base_url}{path}"
|
62
|
-
|
76
|
+
with ssl_handler():
|
77
|
+
response = requests.post(url, headers=self._get_headers(), json=data, files=files, verify=self.verify)
|
63
78
|
self._check_response(response)
|
64
79
|
return response.json() if response.text else {}
|
65
80
|
|
66
81
|
def _post_nd_json(self, path: str, data: dict = None, files: dict = None) -> List[dict]:
|
67
82
|
url = f"{self.base_url}{path}"
|
68
|
-
|
83
|
+
with ssl_handler():
|
84
|
+
response = requests.post(url, headers=self._get_headers(), json=data, files=files)
|
69
85
|
self._check_response(response)
|
70
86
|
|
71
87
|
res = []
|
@@ -76,33 +92,38 @@ class BaseAPIClient:
|
|
76
92
|
|
77
93
|
def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
|
78
94
|
url = f"{self.base_url}{path}"
|
79
|
-
|
80
|
-
|
95
|
+
with ssl_handler():
|
96
|
+
# Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
|
97
|
+
response = requests.post(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
|
81
98
|
self._check_response(response)
|
82
99
|
return response.json() if response.text else {}
|
83
100
|
|
84
101
|
def _put(self, path: str, data: dict = None) -> dict:
|
85
102
|
|
86
103
|
url = f"{self.base_url}{path}"
|
87
|
-
|
104
|
+
with ssl_handler():
|
105
|
+
response = requests.put(url, headers=self._get_headers(), json=data, verify=self.verify)
|
88
106
|
self._check_response(response)
|
89
107
|
return response.json() if response.text else {}
|
90
108
|
|
91
109
|
def _patch(self, path: str, data: dict = None) -> dict:
|
92
110
|
url = f"{self.base_url}{path}"
|
93
|
-
|
111
|
+
with ssl_handler():
|
112
|
+
response = requests.patch(url, headers=self._get_headers(), json=data, verify=self.verify)
|
94
113
|
self._check_response(response)
|
95
114
|
return response.json() if response.text else {}
|
96
115
|
|
97
116
|
def _patch_form_data(self, path: str, data: dict = None, files = None) -> dict:
|
98
117
|
url = f"{self.base_url}{path}"
|
99
|
-
|
118
|
+
with ssl_handler():
|
119
|
+
response = requests.patch(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
|
100
120
|
self._check_response(response)
|
101
121
|
return response.json() if response.text else {}
|
102
122
|
|
103
123
|
def _delete(self, path: str, data=None) -> dict:
|
104
124
|
url = f"{self.base_url}{path}"
|
105
|
-
|
125
|
+
with ssl_handler():
|
126
|
+
response = requests.delete(url, headers=self._get_headers(), json=data, verify=self.verify)
|
106
127
|
self._check_response(response)
|
107
128
|
return response.json() if response.text else {}
|
108
129
|
|
@@ -177,3 +177,17 @@ class ConnectionsClient(BaseAPIClient):
|
|
177
177
|
logger.warning(f"Connections not found. Returning connection ID: {conn_id}")
|
178
178
|
return conn_id
|
179
179
|
raise e
|
180
|
+
|
181
|
+
def get_drafts_by_ids(self, conn_ids) -> List[ListConfigsResponse]:
|
182
|
+
try:
|
183
|
+
res = self._get(f"/connections/applications?connectionIds={','.join(conn_ids)}")
|
184
|
+
import json
|
185
|
+
json.dumps(res)
|
186
|
+
return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
|
187
|
+
except ValidationError as e:
|
188
|
+
logger.error("Recieved unexpected response from server")
|
189
|
+
raise e
|
190
|
+
except ClientAPIException as e:
|
191
|
+
if e.response.status_code == 404:
|
192
|
+
return []
|
193
|
+
raise e
|
@@ -10,7 +10,7 @@ from ibm_cloud_sdk_core.authenticators import MCSPV2Authenticator
|
|
10
10
|
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
|
11
11
|
from ibm_cloud_sdk_core.authenticators import CloudPakForDataAuthenticator
|
12
12
|
|
13
|
-
from ibm_watsonx_orchestrate.client.utils import check_token_validity, is_cpd_env
|
13
|
+
from ibm_watsonx_orchestrate.client.utils import check_token_validity, is_cpd_env, is_ibm_cloud_platform
|
14
14
|
from ibm_watsonx_orchestrate.client.base_service_instance import BaseServiceInstance
|
15
15
|
from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
|
16
16
|
|
@@ -21,14 +21,6 @@ from ibm_watsonx_orchestrate.client.client_errors import (
|
|
21
21
|
import logging
|
22
22
|
logger = logging.getLogger(__name__)
|
23
23
|
|
24
|
-
from ibm_watsonx_orchestrate.cli.config import (
|
25
|
-
Config,
|
26
|
-
CONTEXT_SECTION_HEADER,
|
27
|
-
CONTEXT_ACTIVE_ENV_OPT,
|
28
|
-
ENVIRONMENTS_SECTION_HEADER,
|
29
|
-
ENV_WXO_URL_OPT
|
30
|
-
)
|
31
|
-
|
32
24
|
class ServiceInstance(BaseServiceInstance):
|
33
25
|
"""Connect, get details, and check usage of a Watson Machine Learning service instance."""
|
34
26
|
|
@@ -50,29 +42,28 @@ class ServiceInstance(BaseServiceInstance):
|
|
50
42
|
return self._client.token
|
51
43
|
|
52
44
|
def _create_token(self) -> str:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
45
|
+
inferred_auth_type = None
|
46
|
+
if is_ibm_cloud_platform(self._credentials.url):
|
47
|
+
inferred_auth_type = EnvironmentAuthType.IBM_CLOUD_IAM
|
48
|
+
elif is_cpd_env(self._credentials.url):
|
49
|
+
inferred_auth_type = EnvironmentAuthType.CPD
|
50
|
+
else:
|
51
|
+
inferred_auth_type = EnvironmentAuthType.MCSP
|
52
|
+
|
53
|
+
if self._credentials.auth_type:
|
54
|
+
if self._credentials.auth_type != inferred_auth_type:
|
55
|
+
logger.warning(f"Overriding the default authentication type '{inferred_auth_type}' for url '{self._credentials.url}' with '{self._credentials.auth_type.lower()}'")
|
56
|
+
auth_type = self._credentials.auth_type.lower()
|
57
|
+
else:
|
58
|
+
inferred_type_options = [t for t in EnvironmentAuthType if t != inferred_auth_type]
|
59
|
+
logger.warning(f"Using '{inferred_auth_type}' Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of {', '.join(inferred_type_options[:-1])} or {inferred_type_options[-1]}")
|
60
|
+
auth_type = inferred_auth_type
|
61
|
+
|
67
62
|
if auth_type == "mcsp":
|
68
63
|
try:
|
69
64
|
return self._authenticate(EnvironmentAuthType.MCSP_V1)
|
70
65
|
except:
|
71
66
|
return self._authenticate(EnvironmentAuthType.MCSP_V2)
|
72
|
-
elif auth_type == "mcsp_v1":
|
73
|
-
return self._authenticate(EnvironmentAuthType.MCSP_V1)
|
74
|
-
elif auth_type == "mcsp_v2":
|
75
|
-
return self._authenticate(EnvironmentAuthType.MCSP_V2)
|
76
67
|
else:
|
77
68
|
return self._authenticate(auth_type)
|
78
69
|
|
@@ -100,13 +91,7 @@ class ServiceInstance(BaseServiceInstance):
|
|
100
91
|
if self._credentials.iam_url is not None:
|
101
92
|
url = self._credentials.iam_url
|
102
93
|
else:
|
103
|
-
|
104
|
-
env_cfg = cfg.get(ENVIRONMENTS_SECTION_HEADER)
|
105
|
-
matching_wxo_url = next(
|
106
|
-
(env_config['wxo_url'] for env_config in env_cfg.values() if 'bypass_ssl' in env_config and 'verify' in env_config),
|
107
|
-
None
|
108
|
-
)
|
109
|
-
base_url = matching_wxo_url.split("/orchestrate")[0]
|
94
|
+
base_url = self._credentials.url.split("/orchestrate")[0]
|
110
95
|
url = f"{base_url}/icp4d-api"
|
111
96
|
|
112
97
|
password = self._credentials.password if self._credentials.password is not None else None
|
@@ -66,7 +66,7 @@ def is_ibm_cloud_platform(url:str | None = None) -> bool:
|
|
66
66
|
if url is None:
|
67
67
|
url = get_current_env_url()
|
68
68
|
|
69
|
-
if
|
69
|
+
if ".cloud.ibm.com" in url:
|
70
70
|
return True
|
71
71
|
return False
|
72
72
|
|
@@ -161,6 +161,8 @@ def instantiate_client(client: type[T] , url: str | None=None) -> T:
|
|
161
161
|
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
|
162
162
|
elif verify is not None:
|
163
163
|
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url), verify=verify)
|
164
|
+
else:
|
165
|
+
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url))
|
164
166
|
else:
|
165
167
|
client_instance = client(base_url=url, api_key=token, is_local=is_local_dev(url))
|
166
168
|
|
@@ -133,7 +133,7 @@ services:
|
|
133
133
|
DOCPROC_ENABLED: ${DOCPROC_ENABLED:-false}
|
134
134
|
IS_OBSERVABILITY_FEATURE_ENABLED: "true"
|
135
135
|
ALLOW_INSECURE_TLS: "true"
|
136
|
-
ENABLE_EDIT_PROMPTS: "
|
136
|
+
ENABLE_EDIT_PROMPTS: "false"
|
137
137
|
LANGFLOW_ENABLED: ${LANGFLOW_ENABLED:-false}
|
138
138
|
command: 'npm start'
|
139
139
|
ports:
|
@@ -378,6 +378,7 @@ services:
|
|
378
378
|
IS_WXO_LITE: "TRUE"
|
379
379
|
TRM_BASE_URL: http://tools-runtime-manager:8080
|
380
380
|
AGENT_STEP_DETAILS: redis://wxo-server-redis:6379/0
|
381
|
+
RECURSION_LIMIT: ${RECURSION_LIMIT}
|
381
382
|
AGENT_GATEWAY_URI: http://wxo-agent-gateway:8989
|
382
383
|
JWT_SECRET: ${JWT_SECRET}
|
383
384
|
POSTGRES_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@wxo-server-db:5432/postgres
|
@@ -886,18 +887,18 @@ services:
|
|
886
887
|
PREFLIGHT_MAX_SIZE: 10Mb
|
887
888
|
PREFLIGHT_MAX_PAGES: 600
|
888
889
|
RUNTIME_LIBRARY: "watson_doc_understanding"
|
889
|
-
WXAI_API_KEY: ${WXAI_API_KEY:-}
|
890
|
-
WXAI_IMAGE_DESCRIPTION_MODEL_ID: ${WXAI_IMAGE_DESCRIPTION_MODEL_ID:-mistralai/
|
891
|
-
|
892
|
-
WXAI_KVP_MODEL_ID: ${WXAI_KVP_MODEL_ID:-}
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
WXAI_URL: ${WXAI_URL:-}
|
890
|
+
WXAI_API_KEY: ${WXAI_API_KEY:-"gateway"}
|
891
|
+
WXAI_IMAGE_DESCRIPTION_MODEL_ID: ${WXAI_IMAGE_DESCRIPTION_MODEL_ID:-mistralai/mistral-small-3-1-24b-instruct-2503}
|
892
|
+
WXAI_IMAGE_DESCRIPTION_SPACE_ID: ${WATSONX_SPACE_ID:-}
|
893
|
+
WXAI_KVP_MODEL_ID: ${WXAI_KVP_MODEL_ID:-mistralai/mistral-small-3-1-24b-instruct-2503}
|
894
|
+
WXAI_KVP_SPACE_ID: ${WATSONX_SPACE_ID:-}
|
895
|
+
WXAI_SEMANTIC_KVP_MODEL_ID: ${WXAI_SEMANTIC_KVP_MODEL_ID:-mistralai/mistral-small-3-1-24b-instruct-2503}
|
896
|
+
WXAI_SEMANTIC_KVP_SPACE_ID: ${WATSONX_SPACE_ID:-}
|
897
|
+
WXAI_URL: ${AI_GATEWAY_BASE_URL:-}
|
898
898
|
WXAI_USERNAME: ${WXAI_USERNAME:-}
|
899
899
|
WXAI_INSTANCE_ID: ${WXAI_INSTANCE_ID:-}
|
900
|
-
WXAI_VERSION: ${WXAI_VERSION:-
|
900
|
+
WXAI_VERSION: ${WXAI_VERSION:-2023-05-29}
|
901
|
+
WXAI_PROVIDER: ${WXAI_PROVIDER:-gateway}
|
901
902
|
|
902
903
|
profiles:
|
903
904
|
- docproc
|