ibm-watsonx-orchestrate 1.11.0b0__py3-none-any.whl → 1.11.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +13 -0
  3. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +5 -2
  4. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +24 -9
  5. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +10 -2
  6. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +404 -173
  7. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +6 -2
  8. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +1 -1
  9. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +174 -2
  10. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +93 -9
  11. ibm_watsonx_orchestrate/client/base_api_client.py +31 -10
  12. ibm_watsonx_orchestrate/client/connections/connections_client.py +14 -0
  13. ibm_watsonx_orchestrate/client/service_instance.py +19 -34
  14. ibm_watsonx_orchestrate/client/utils.py +3 -1
  15. ibm_watsonx_orchestrate/docker/compose-lite.yml +2 -1
  16. ibm_watsonx_orchestrate/docker/default.env +10 -10
  17. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +3 -1
  18. ibm_watsonx_orchestrate/flow_builder/types.py +252 -1
  19. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.11.0b1.dist-info}/METADATA +2 -2
  20. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.11.0b1.dist-info}/RECORD +23 -23
  21. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.11.0b1.dist-info}/WHEEL +0 -0
  22. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.11.0b1.dist-info}/entry_points.txt +0 -0
  23. {ibm_watsonx_orchestrate-1.11.0b0.dist-info → ibm_watsonx_orchestrate-1.11.0b1.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(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,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 analyze
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
- config = TestConfig(**config_data)
94
-
95
- evaluate.main(config)
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(data_path=data_path)
165
- analyze(config)
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)
@@ -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
- response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
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
- response = requests.post(url, headers=self._get_headers(), json=data, files=files, verify=self.verify)
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
- response = requests.post(url, headers=self._get_headers(), json=data, files=files)
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
- # Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
80
- response = requests.post(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
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
- response = requests.put(url, headers=self._get_headers(), json=data, verify=self.verify)
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
- response = requests.patch(url, headers=self._get_headers(), json=data, verify=self.verify)
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
- response = requests.patch(url, headers=self._get_headers(), data=data, files=files, verify=self.verify)
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
- response = requests.delete(url, headers=self._get_headers(), json=data, verify=self.verify)
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
- if not self._credentials.auth_type:
54
- if ".cloud.ibm.com" in self._credentials.url:
55
- logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'mcsp', 'mcsp_v1', 'mcsp_v2' or 'cpd' ")
56
- return self._authenticate(EnvironmentAuthType.IBM_CLOUD_IAM)
57
- elif is_cpd_env(self._credentials.url):
58
- logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam', 'mcsp', 'mcsp_v1' or 'mcsp_v2' ")
59
- return self._authenticate(EnvironmentAuthType.CPD)
60
- else:
61
- logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam', 'mcsp_v1', 'mcsp_v2' or 'cpd' ")
62
- try:
63
- return self._authenticate(EnvironmentAuthType.MCSP_V1)
64
- except:
65
- return self._authenticate(EnvironmentAuthType.MCSP_V2)
66
- auth_type = self._credentials.auth_type.lower()
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
- cfg = Config()
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 url.__contains__("cloud.ibm.com"):
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: "true"
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
@@ -36,7 +36,7 @@ DB_ENCRYPTION_KEY=dummy_db_encryption_key
36
36
  BASE_URL=dummy_base_url
37
37
  SERVER_TYPE=CELERY
38
38
  SQLALCHEMY_DEBUG=false
39
-
39
+ RECURSION_LIMIT=50
40
40
  LANGFUSE_HOST=http://host.docker.internal:3010
41
41
  LANGFUSE_EMAIL=orchestrate@ibm.com
42
42
  LANGFUSE_USERNAME=orchestrate
@@ -58,10 +58,10 @@ REGISTRY_URL=
58
58
 
59
59
 
60
60
 
61
- SERVER_TAG=29-08-2025-59ef405
61
+ SERVER_TAG=05-09-2025-Hb2zH5Ggf
62
62
  SERVER_REGISTRY=
63
63
 
64
- WORKER_TAG=29-08-2025-cf32b2d
64
+ WORKER_TAG=05-09-2025-Hb2zH5Ggf
65
65
  WORKER_REGISTRY=
66
66
 
67
67
  AI_GATEWAY_TAG=20-08-2025-9ed6d40
@@ -73,9 +73,9 @@ AGENT_GATEWAY_REGISTRY=
73
73
  DB_REGISTRY=
74
74
  # If you build multiarch set all three of these to the same, we have a pr against main
75
75
  # to not have this separation, but we can merge it later
76
- DBTAG=29-07-2025-9f3661b
77
- AMDDBTAG=29-07-2025-9f3661b
78
- ARM64DBTAG=29-07-2025-9f3661b
76
+ DBTAG=04-09-2025-48c39
77
+ AMDDBTAG=04-09-2025-48c39
78
+ ARM64DBTAG=04-09-2025-48c39
79
79
 
80
80
  UI_REGISTRY=
81
81
  UITAG=29-08-2025
@@ -105,7 +105,7 @@ JAEGER_PROXY_REGISTRY=
105
105
  SOCKET_HANDLER_TAG=29-05-2025
106
106
  SOCKET_HANDLER_REGISTRY=
107
107
 
108
- CPE_TAG=18-08-2025-ae1308e
108
+ CPE_TAG=29-08-2025-e612bea
109
109
  CPE_REGISTRY=
110
110
 
111
111
  VOICE_CONTROLLER_TAG=12-08-2025
@@ -118,10 +118,10 @@ LANGFLOW_IMAGE=
118
118
  WDU_TAG=2.6.1
119
119
  WDU_REGISTRY=
120
120
 
121
- DOCPROC_DPS_TAG=20250815-010747-277-173db2a
122
- DOCPROC_LLMSERVICE_TAG=20250820-153924-128-55cf4d5
121
+ DOCPROC_DPS_TAG=20250826-201302-283-24eb106
122
+ DOCPROC_LLMSERVICE_TAG=20250826-main-133-42a5320
123
123
  DOCPROC_CACHE_TAG=20250814-master-82-cf33f87
124
- DOCPROC_DPI_TAG=20250815-004755-273-e65f26b4
124
+ DOCPROC_DPI_TAG=20250828-154151-285-e4dd1cff
125
125
  DOCPROC_REGISTRY=
126
126
 
127
127
  # END -- IMAGE REGISTRIES AND TAGS
@@ -440,7 +440,7 @@ class Flow(Node):
440
440
  node = self._add_node(node)
441
441
  return cast(PromptNode, node)
442
442
 
443
- def docclassfier(self,
443
+ def docclassifier(self,
444
444
  name: str,
445
445
  llm : str = "watsonx/meta-llama/llama-3-2-90b-vision-instruct",
446
446
  version: str = "TIP",
@@ -607,6 +607,7 @@ class Flow(Node):
607
607
  display_name: str|None=None,
608
608
  description: str | None = None,
609
609
  input_map: DataMap = None,
610
+ document_structure: bool = False,
610
611
  kvp_schemas: list[DocProcKVPSchema] = None,
611
612
  enable_hw: bool = False) -> DocProcNode:
612
613
 
@@ -631,6 +632,7 @@ class Flow(Node):
631
632
  output_schema=_get_tool_response_body(output_schema_obj),
632
633
  output_schema_object = output_schema_obj,
633
634
  task=task,
635
+ document_structure=document_structure,
634
636
  plain_text_reading_order=plain_text_reading_order,
635
637
  enable_hw=enable_hw,
636
638
  kvp_schemas=kvp_schemas