ibm-watsonx-orchestrate 1.10.0b1__py3-none-any.whl → 1.10.1__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.
@@ -5,8 +5,7 @@
5
5
 
6
6
  pkg_name = "ibm-watsonx-orchestrate"
7
7
 
8
- __version__ = "1.10.0b1"
9
-
8
+ __version__ = "1.10.1"
10
9
 
11
10
 
12
11
 
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import logging
3
+ from copy import deepcopy
3
4
  from typing import List
4
5
  from ibm_watsonx_orchestrate.agent_builder.connections.types import (
5
6
  BasicAuthCredentials,
@@ -67,7 +68,8 @@ def _clean_env_vars(vars: dict[str:str], requirements: List[str], app_id: str) -
67
68
  return required_env_vars
68
69
 
69
70
  def _build_credentials_model(credentials_type: type[CREDENTIALS], vars: dict[str,str], base_prefix: str) -> type[CREDENTIALS]:
70
- requirements = connection_type_requirements_mapping[credentials_type]
71
+ requirements_lut = deepcopy(connection_type_requirements_mapping)
72
+ requirements = requirements_lut[credentials_type]
71
73
 
72
74
  if requirements:
73
75
  requirements.append("url")
@@ -101,7 +103,8 @@ def _get_credentials_model(connection_type: ConnectionSecurityScheme, app_id: st
101
103
 
102
104
  credentials_type = CONNECTION_TYPE_CREDENTIAL_MAPPING[connection_type]
103
105
 
104
- requirements = connection_type_requirements_mapping.get(credentials_type)
106
+ requirements_lut = deepcopy(connection_type_requirements_mapping)
107
+ requirements = requirements_lut.get(credentials_type)
105
108
  if requirements:
106
109
  variables = _clean_env_vars(vars=variables, requirements=requirements, app_id=app_id)
107
110
 
@@ -165,6 +165,53 @@ class ConnectionConfiguration(BaseModel):
165
165
  raise ValueError("Connection of type 'key_value' cannot be configured at the 'member' level. Key value connections must be of type 'team'")
166
166
  return self
167
167
 
168
+ class ConnectionCredentialsEntryLocation(str, Enum):
169
+ BODY = 'body'
170
+ HEADER = 'header',
171
+ QUERY = 'query'
172
+
173
+ def __str__(self):
174
+ return self.value
175
+
176
+ class ConnectionCredentialsEntry(BaseModel):
177
+ key: str
178
+ value: str
179
+ location: ConnectionCredentialsEntryLocation
180
+
181
+ def __str__(self):
182
+ return f"<ConnectionCredentialsEntry: {self.location}:{self.key}={self.value}>"
183
+
184
+ class BaseOAuthCredentials(BaseModel):
185
+ custom_token_query: Optional[dict] = None
186
+ custom_token_header: Optional[dict] = None
187
+ custom_token_body: Optional[dict] = None
188
+ custom_auth_query: Optional[dict] = None
189
+
190
+ class ConnectionCredentialsCustomFields(BaseOAuthCredentials):
191
+ def add_field(self, entry: ConnectionCredentialsEntry, is_token:bool=True) -> None:
192
+ match entry.location:
193
+ case ConnectionCredentialsEntryLocation.HEADER:
194
+ if not is_token:
195
+ return
196
+ attribute = "custom_token_header"
197
+ case ConnectionCredentialsEntryLocation.BODY:
198
+ if not is_token:
199
+ return
200
+ attribute = "custom_token_body"
201
+ case ConnectionCredentialsEntryLocation.QUERY:
202
+ if is_token:
203
+ attribute = "custom_token_query"
204
+ else:
205
+ attribute = "custom_auth_query"
206
+ case _:
207
+ return
208
+
209
+ fields = getattr(self, attribute)
210
+ if not fields:
211
+ setattr(self, attribute, {})
212
+ fields = getattr(self, attribute)
213
+ fields[entry.key] = entry.value
214
+
168
215
  class BasicAuthCredentials(BaseModel):
169
216
  username: str
170
217
  password: str
@@ -182,7 +229,7 @@ class OAuth2TokenCredentials(BaseModel):
182
229
  access_token: str
183
230
  url: Optional[str] = None
184
231
 
185
- class OAuth2AuthCodeCredentials(BaseModel):
232
+ class OAuth2AuthCodeCredentials(BaseOAuthCredentials):
186
233
  client_id: str
187
234
  client_secret: str
188
235
  token_url: str
@@ -193,7 +240,7 @@ class OAuth2AuthCodeCredentials(BaseModel):
193
240
  # client_id: str
194
241
  # authorization_url: str
195
242
 
196
- class OAuth2PasswordCredentials(BaseModel):
243
+ class OAuth2PasswordCredentials(BaseOAuthCredentials):
197
244
  username: str
198
245
  password: str
199
246
  client_id: str
@@ -203,7 +250,7 @@ class OAuth2PasswordCredentials(BaseModel):
203
250
  grant_type: str = "password"
204
251
 
205
252
 
206
- class OAuth2ClientCredentials(BaseModel):
253
+ class OAuth2ClientCredentials(BaseOAuthCredentials):
207
254
  client_id: str
208
255
  client_secret: str
209
256
  token_url: str
@@ -211,7 +258,7 @@ class OAuth2ClientCredentials(BaseModel):
211
258
  send_via: ConnectionSendVia = ConnectionSendVia.HEADER
212
259
  grant_type: str = "client_credentials"
213
260
 
214
- class OAuthOnBehalfOfCredentials(BaseModel):
261
+ class OAuthOnBehalfOfCredentials(BaseOAuthCredentials):
215
262
  client_id: str
216
263
  access_token_url: str
217
264
  grant_type: str
@@ -267,7 +314,7 @@ CONNECTION_TYPE_CREDENTIAL_MAPPING = {
267
314
  ConnectionSecurityScheme.KEY_VALUE: KeyValueConnectionCredentials,
268
315
  }
269
316
 
270
- class IdentityProviderCredentials(BaseModel):
317
+ class IdentityProviderCredentials(BaseOAuthCredentials):
271
318
  idp_url: str = Field(validation_alias=AliasChoices('idp_url', 'url'), serialization_alias='idp_url')
272
319
  client_id: str
273
320
  client_secret: str
@@ -276,4 +323,4 @@ class IdentityProviderCredentials(BaseModel):
276
323
 
277
324
  class ExpectedCredentials(BaseModel):
278
325
  app_id: str
279
- type: ConnectionType | List[ConnectionType]
326
+ type: ConnectionType | List[ConnectionType]
@@ -1,6 +1,6 @@
1
1
  import typer
2
2
  from typing_extensions import Annotated, List
3
- from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionKind
3
+ from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionKind, ConnectionCredentialsEntry
4
4
  from ibm_watsonx_orchestrate.cli.commands.connections.connections_controller import (
5
5
  add_connection,
6
6
  remove_connection,
@@ -8,7 +8,9 @@ from ibm_watsonx_orchestrate.cli.commands.connections.connections_controller imp
8
8
  import_connection,
9
9
  configure_connection,
10
10
  set_credentials_connection,
11
- set_identity_provider_connection
11
+ set_identity_provider_connection,
12
+ token_entry_connection_credentials_parse,
13
+ auth_entry_connection_credentials_parse
12
14
  )
13
15
 
14
16
  connections_app = typer.Typer(no_args_is_help=True)
@@ -244,6 +246,22 @@ def set_credentials_connection_command(
244
246
  help="For key_value, a key value pair in the form '<key>=<value>'. Multiple values can be passed using `-e key1=value1 -e key2=value2`"
245
247
  )
246
248
  ] = None,
249
+ token_entries: Annotated[
250
+ List[ConnectionCredentialsEntry],
251
+ typer.Option(
252
+ '--token-entries', "-t",
253
+ parser=token_entry_connection_credentials_parse,
254
+ help="Custom field options for oauth types token request, a key value location option in the form 'location:<key>=<value>' or '<key>=<value>' with location defaulting to 'header'. Multiple values can be passed using `-t key1=value1 -t location:key2=value2`"
255
+ )
256
+ ] = None,
257
+ auth_entries: Annotated[
258
+ List[ConnectionCredentialsEntry],
259
+ typer.Option(
260
+ '--auth-entries',
261
+ parser=auth_entry_connection_credentials_parse,
262
+ help="Custom field options for oauth_auth_code_flow auth server request, a key value location option in the form 'location:<key>=<value>' or '<key>=<value>' with location defaulting to 'query'. Note only 'query' is a valid location. Multiple values can be passed using `--auth-entries key1=value1 --auth-entries location:key2=value2`"
263
+ )
264
+ ] = None,
247
265
  ):
248
266
  set_credentials_connection(
249
267
  app_id=app_id,
@@ -259,7 +277,9 @@ def set_credentials_connection_command(
259
277
  auth_url=auth_url,
260
278
  grant_type=grant_type,
261
279
  scope=scope,
262
- entries=entries
280
+ entries=entries,
281
+ token_entries=token_entries,
282
+ auth_entries=auth_entries
263
283
  )
264
284
 
265
285
  @connections_app.command(name="set-identity-provider")
@@ -311,6 +331,14 @@ def set_identity_provider_connection_command(
311
331
  help='The grant-type of the the identity provider'
312
332
  )
313
333
  ],
334
+ token_entries: Annotated[
335
+ List[ConnectionCredentialsEntry],
336
+ typer.Option(
337
+ '--token-entries', "-t",
338
+ parser=token_entry_connection_credentials_parse,
339
+ help="Custom field options for oauth types token request, a key value location option in the form 'location:<key>=<value>' or '<key>=<value>' with location defaulting to 'header'. Multiple values can be passed using `-t key1=value1 -t location:key2=value2`"
340
+ )
341
+ ] = None,
314
342
  ):
315
343
  set_identity_provider_connection(
316
344
  app_id=app_id,
@@ -319,5 +347,6 @@ def set_identity_provider_connection_command(
319
347
  client_id=client_id,
320
348
  client_secret=client_secret,
321
349
  scope=scope,
322
- grant_type=grant_type
350
+ grant_type=grant_type,
351
+ token_entries=token_entries
323
352
  )
@@ -27,7 +27,10 @@ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
27
27
  KeyValueConnectionCredentials,
28
28
  CREDENTIALS,
29
29
  IdentityProviderCredentials,
30
- OAUTH_CONNECTION_TYPES
30
+ OAUTH_CONNECTION_TYPES,
31
+ ConnectionCredentialsEntryLocation,
32
+ ConnectionCredentialsEntry,
33
+ ConnectionCredentialsCustomFields
31
34
 
32
35
  )
33
36
 
@@ -167,6 +170,13 @@ def _validate_connection_params(type: ConnectionType, **args) -> None:
167
170
  f"Missing flags --grant-type is required for type {type}"
168
171
  )
169
172
 
173
+ if type != ConnectionType.OAUTH2_AUTH_CODE and (
174
+ args.get('auth_entries')
175
+ ):
176
+ raise typer.BadParameter(
177
+ f"The flag --auth-entries is only supported by type {type}"
178
+ )
179
+
170
180
 
171
181
  def _parse_entry(entry: str) -> dict[str,str]:
172
182
  split_entry = entry.split('=', 1)
@@ -176,6 +186,19 @@ def _parse_entry(entry: str) -> dict[str,str]:
176
186
  exit(1)
177
187
  return {split_entry[0]: split_entry[1]}
178
188
 
189
+ def _get_oauth_custom_fields(token_entries: List[ConnectionCredentialsEntry] | None, auth_entries: List[ConnectionCredentialsEntry] | None) -> dict:
190
+ custom_fields = ConnectionCredentialsCustomFields()
191
+
192
+ if token_entries:
193
+ for entry in token_entries:
194
+ custom_fields.add_field(entry, is_token=True)
195
+
196
+ if auth_entries:
197
+ for entry in auth_entries:
198
+ custom_fields.add_field(entry, is_token=False)
199
+
200
+ return custom_fields.model_dump(exclude_none=True)
201
+
179
202
  def _get_credentials(type: ConnectionType, **kwargs):
180
203
  match type:
181
204
  case ConnectionType.BASIC_AUTH:
@@ -192,18 +215,21 @@ def _get_credentials(type: ConnectionType, **kwargs):
192
215
  api_key=kwargs.get("api_key")
193
216
  )
194
217
  case ConnectionType.OAUTH2_AUTH_CODE:
218
+ custom_fields = _get_oauth_custom_fields(kwargs.get("token_entries"), kwargs.get("auth_entries"))
195
219
  return OAuth2AuthCodeCredentials(
196
220
  authorization_url=kwargs.get("auth_url"),
197
221
  client_id=kwargs.get("client_id"),
198
222
  client_secret=kwargs.get("client_secret"),
199
223
  token_url=kwargs.get("token_url"),
200
- scope=kwargs.get("scope")
224
+ scope=kwargs.get("scope"),
225
+ **custom_fields
201
226
  )
202
227
  case ConnectionType.OAUTH2_CLIENT_CREDS:
203
228
  # using filtered args as default values will not be set if 'None' is passed, causing validation errors
204
229
  keys = ["client_id","client_secret","token_url","grant_type","send_via", "scope"]
205
230
  filtered_args = { key_name: kwargs[key_name] for key_name in keys if kwargs.get(key_name) }
206
- return OAuth2ClientCredentials(**filtered_args)
231
+ custom_fields = _get_oauth_custom_fields(kwargs.get("token_entries"), kwargs.get("auth_entries"))
232
+ return OAuth2ClientCredentials(**filtered_args, **custom_fields)
207
233
  # case ConnectionType.OAUTH2_IMPLICIT:
208
234
  # return OAuth2ImplicitCredentials(
209
235
  # authorization_url=kwargs.get("auth_url"),
@@ -212,13 +238,16 @@ def _get_credentials(type: ConnectionType, **kwargs):
212
238
  case ConnectionType.OAUTH2_PASSWORD:
213
239
  keys = ["username", "password", "client_id","client_secret","token_url","grant_type", "scope"]
214
240
  filtered_args = { key_name: kwargs[key_name] for key_name in keys if kwargs.get(key_name) }
215
- return OAuth2PasswordCredentials(**filtered_args)
241
+ custom_fields = _get_oauth_custom_fields(kwargs.get("token_entries"), kwargs.get("auth_entries"))
242
+ return OAuth2PasswordCredentials(**filtered_args, **custom_fields)
216
243
 
217
244
  case ConnectionType.OAUTH_ON_BEHALF_OF_FLOW:
245
+ custom_fields = _get_oauth_custom_fields(kwargs.get("token_entries"), kwargs.get("auth_entries"))
218
246
  return OAuthOnBehalfOfCredentials(
219
247
  client_id=kwargs.get("client_id"),
220
248
  access_token_url=kwargs.get("token_url"),
221
- grant_type=kwargs.get("grant_type")
249
+ grant_type=kwargs.get("grant_type"),
250
+ **custom_fields
222
251
  )
223
252
  case ConnectionType.KEY_VALUE:
224
253
  env = {}
@@ -231,6 +260,23 @@ def _get_credentials(type: ConnectionType, **kwargs):
231
260
  case _:
232
261
  raise ValueError(f"Invalid type '{type}' selected")
233
262
 
263
+ def _connection_credentials_parse_entry(text: str, default_location: ConnectionCredentialsEntryLocation) -> ConnectionCredentialsEntry:
264
+ location_kv_pair = text.split(":", 1)
265
+ key_value = location_kv_pair[-1]
266
+ location = location_kv_pair[0] if len(location_kv_pair)>1 else default_location
267
+
268
+ valid_locations = [item.value for item in ConnectionCredentialsEntryLocation]
269
+ if location not in valid_locations:
270
+ raise typer.BadParameter(f"The provided location '{location}' is not in the allowed values {valid_locations}.")
271
+
272
+ key_value_pair = key_value.split('=', 1)
273
+ if len(key_value_pair) != 2:
274
+ message = f"The entry '{text}' is not in the expected form '<location>:<key>=<value>' or '<key>=<value>'"
275
+ raise typer.BadParameter(message)
276
+ key, value = key_value_pair[0], key_value_pair[1]
277
+
278
+ return ConnectionCredentialsEntry(key=key, value=value, location=location)
279
+
234
280
 
235
281
  def add_configuration(config: ConnectionConfiguration) -> None:
236
282
  client = get_connections_client()
@@ -524,5 +570,15 @@ def set_identity_provider_connection(
524
570
  logger.error(f"Cannot set Identity Provider when 'sso' is false in configuration. Please enable sso for connection '{app_id}' in environment '{environment}' and try again.")
525
571
  sys.exit(1)
526
572
 
527
- idp = IdentityProviderCredentials.model_validate(kwargs)
573
+ custom_fields = _get_oauth_custom_fields(token_entries=kwargs.get("token_entries"), auth_entries=None)
574
+ idp = IdentityProviderCredentials(**kwargs, **custom_fields)
528
575
  add_identity_provider(app_id=app_id, environment=environment, idp=idp)
576
+
577
+ def token_entry_connection_credentials_parse(text: str) -> ConnectionCredentialsEntry:
578
+ return _connection_credentials_parse_entry(text=text, default_location=ConnectionCredentialsEntryLocation.HEADER)
579
+
580
+ def auth_entry_connection_credentials_parse(text: str) -> ConnectionCredentialsEntry:
581
+ entry = _connection_credentials_parse_entry(text=text, default_location=ConnectionCredentialsEntryLocation.QUERY)
582
+ if entry.location != ConnectionCredentialsEntryLocation.QUERY:
583
+ raise typer.BadParameter(f"Only location '{ConnectionCredentialsEntryLocation.QUERY}' is supported for --auth-entry")
584
+ return entry
@@ -191,9 +191,6 @@ def get_default_registry_env_vars_by_dev_edition_source(default_env: dict, user_
191
191
  parsed = urlparse(wo_url)
192
192
  hostname = parsed.hostname
193
193
 
194
- if not hostname or not hostname.startswith("api."):
195
- raise ValueError(f"Invalid WO_INSTANCE URL: '{wo_url}'. It should starts with 'api.'")
196
-
197
194
  registry_url = f"registry.{hostname[4:]}/cp/wxo-lite"
198
195
  else:
199
196
  raise ValueError(f"Unknown value for developer edition source: {source}. Must be one of ['internal', 'myibm', 'orchestrate'].")
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import sys
3
+ import uuid
3
4
  from enum import Enum
4
5
  from pydantic import BaseModel, model_validator, ConfigDict
5
6
 
@@ -43,9 +44,6 @@ class WatsonXAIEnvConfig(BaseModel):
43
44
  if not config.get("WATSONX_SPACE_ID") and not config.get("WATSONX_APIKEY"):
44
45
  raise ValueError("Missing configuration requirements 'WATSONX_SPACE_ID' and 'WATSONX_APIKEY'")
45
46
 
46
- if config.get("WATSONX_SPACE_ID") and not config.get("WATSONX_APIKEY"):
47
- logger.error("Cannot use env var 'WATSONX_SPACE_ID' without setting the corresponding 'WATSONX_APIKEY'")
48
- sys.exit(1)
49
47
 
50
48
  if not config.get("WATSONX_SPACE_ID") and config.get("WATSONX_APIKEY"):
51
49
  logger.error("Cannot use env var 'WATSONX_APIKEY' without setting the corresponding 'WATSONX_SPACE_ID'")
@@ -54,6 +52,12 @@ class WatsonXAIEnvConfig(BaseModel):
54
52
  config["USE_SAAS_ML_TOOLS_RUNTIME"] = False
55
53
  return config
56
54
 
55
+ def is_valid_uuid(value) -> bool:
56
+ try:
57
+ uuid.UUID(str(value))
58
+ return True
59
+ except (ValueError, TypeError, AttributeError):
60
+ return False
57
61
 
58
62
  class ModelGatewayEnvConfig(BaseModel):
59
63
  WO_API_KEY: str | None = None
@@ -84,7 +88,10 @@ class ModelGatewayEnvConfig(BaseModel):
84
88
  if not config.get("AUTHORIZATION_URL"):
85
89
  inferred_auth_url = AUTH_TYPE_DEFAULT_URL_MAPPING.get(auth_type)
86
90
  if not inferred_auth_url:
87
- logger.error(f"No 'AUTHORIZATION_URL' found. Auth type '{auth_type}' does not support defaulting. Please set the 'AUTHORIZATION_URL' explictly")
91
+ if auth_type == WoAuthType.CPD:
92
+ inferred_auth_url = config.get("WO_INSTANCE") + '/icp4d-api/v1/authorize'
93
+ else:
94
+ logger.error(f"No 'AUTHORIZATION_URL' found. Auth type '{auth_type}' does not support defaulting. Please set the 'AUTHORIZATION_URL' explictly")
88
95
  sys.exit(1)
89
96
  config["AUTHORIZATION_URL"] = inferred_auth_url
90
97
 
@@ -101,6 +108,7 @@ class ModelGatewayEnvConfig(BaseModel):
101
108
  sys.exit(1)
102
109
 
103
110
  config["USE_SAAS_ML_TOOLS_RUNTIME"] = True
104
- # Fake (but valid) UUIDv4 for knowledgebase check
105
- config["WATSONX_SPACE_ID"] = "aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa"
111
+ if not is_valid_uuid(config.get("WATSONX_SPACE_ID")):
112
+ # Fake (but valid) UUIDv4 for knowledgebase check
113
+ config["WATSONX_SPACE_ID"] = "aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa"
106
114
  return config
@@ -62,6 +62,7 @@ services:
62
62
  WATSONX_API_KEY: ${WATSONX_APIKEY}
63
63
  WATSONX_URL: ${WATSONX_URL}
64
64
  WATSONX_SPACE_ID: ${WATSONX_SPACE_ID}
65
+ NODE_TLS_REJECT_UNAUTHORIZED: ${AI_GATEWAY_TLS_REJECT_UNAUTHORIZED}
65
66
 
66
67
  wxo-agent-gateway:
67
68
  image: ${AGENT_GATEWAY_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-agent-gateway:${AGENT_GATEWAY_TAG:-latest}
@@ -327,6 +328,7 @@ services:
327
328
  ENABLE_WEBHOOKS: false
328
329
  DISABLE_JSON_LOG_CELERY: true
329
330
  WXO_DEPLOYMENT_PLATFORM: saas
331
+ CPD_VERIFY: ${CPD_VERIFY}
330
332
  CONNECTION_MANAGER_URL: http://wxo-server-connection-manager:3001
331
333
  CHANNEL_SESSION_REDIS_URL: redis://wxo-server-redis:6379/5
332
334
  WXO_MILVUS_URI: http://wxo-milvus-standalone:19530
@@ -446,6 +448,7 @@ services:
446
448
  IBM_TELEMETRY_TRACER_ENDPOINT: http://jaeger:4318/v1/traces
447
449
  USE_IBM_TELEMETRY: ${USE_IBM_TELEMETRY:-false}
448
450
  WXO_DEPLOYMENT_PLATFORM: saas
451
+ CPD_VERIFY: ${CPD_VERIFY}
449
452
  CALLBACK_HOST_URL: ${CALLBACK_HOST_URL:-http://wxo-server:4321}
450
453
  LANGFLOW_ENABLED: ${LANGFLOW_ENABLED:-false}
451
454
 
@@ -653,6 +656,7 @@ services:
653
656
  - WATSONX_URL=${WATSONX_URL}
654
657
  - PROXY_SERVER_URL=http://jaeger-proxy:9201
655
658
  - WXO_DEPLOYMENT_PLATFORM=saas
659
+ - CPD_VERIFY=${CPD_VERIFY}
656
660
  - TENANT_API_KEY=${AGENTOPS_API_KEY}
657
661
  - TENANT_CONFIG_URL=http://wxo-server:4321
658
662
  - TENANT_DEFAULT_USERNAME=${ES_USERNAME}
@@ -58,10 +58,10 @@ REGISTRY_URL=
58
58
 
59
59
 
60
60
 
61
- SERVER_TAG=20-08-2025-cf6e342
61
+ SERVER_TAG=29-08-2025-59ef405
62
62
  SERVER_REGISTRY=
63
63
 
64
- WORKER_TAG=20-08-2025-cf6e342
64
+ WORKER_TAG=29-08-2025-cf32b2d
65
65
  WORKER_REGISTRY=
66
66
 
67
67
  AI_GATEWAY_TAG=20-08-2025-9ed6d40
@@ -78,7 +78,7 @@ AMDDBTAG=29-07-2025-9f3661b
78
78
  ARM64DBTAG=29-07-2025-9f3661b
79
79
 
80
80
  UI_REGISTRY=
81
- UITAG=31-07-2025
81
+ UITAG=29-08-2025
82
82
 
83
83
  CM_REGISTRY=
84
84
  CM_TAG=24-07-2025
@@ -86,13 +86,13 @@ CM_TAG=24-07-2025
86
86
  TRM_TAG=19-08-2025-fe105eb0b950ff304f712a1a5b9fa3cba92d09da
87
87
  TRM_REGISTRY=
88
88
 
89
- TR_TAG=19-08-2025-fe105eb0b950ff304f712a1a5b9fa3cba92d09da
89
+ TR_TAG=25-08-2025-58ae475
90
90
  TR_REGISTRY=
91
91
 
92
- BUILDER_TAG=19-08-2025-1a79d34
92
+ BUILDER_TAG=27-08-2025-7432aca
93
93
  BUILDER_REGISTRY=
94
94
 
95
- FLOW_RUNTIME_TAG=18-08-2025-v2
95
+ FLOW_RUNTIME_TAG=26-08-2025-3b7f19e
96
96
  FLOW_RUMTIME_REGISTRY=
97
97
 
98
98
 
@@ -126,6 +126,8 @@ DOCPROC_REGISTRY=
126
126
 
127
127
  # END -- IMAGE REGISTRIES AND TAGS
128
128
 
129
+ CPD_VERIFY=true
130
+ AI_GATEWAY_TLS_REJECT_UNAUTHORIZED=1
129
131
  TAVILY_API_KEY=dummy_tavily_api_key
130
132
  PREFERRED_MODELS=meta-llama/llama-3-2-90b-vision-instruct,meta-llama/llama-3-405b-instruct
131
133
  INCOMPATIBLE_MODELS=flan,embedding,cross-encoder,tinytimemixers
@@ -12,7 +12,10 @@ class DataMap(BaseModel):
12
12
  def to_json(self) -> dict[str, Any]:
13
13
  model_spec = {}
14
14
  if self.maps and len(self.maps) > 0:
15
- model_spec["maps"] = [assignment.model_dump() for assignment in self.maps]
15
+ model_spec["maps"] = []
16
+ for assignment in self.maps:
17
+ model_spec["maps"].append(assignment.model_dump())
18
+ return model_spec
16
19
 
17
20
  def add(self, line: Assignment) -> Self:
18
21
  self.maps.append(line)
@@ -25,7 +25,7 @@ from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
25
25
  from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
26
26
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
27
27
  from ..types import (
28
- DocProcKVPSchema, EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy, PlainTextReadingOrder, PromptLLMParameters, PromptNodeSpec, TimerNodeSpec,
28
+ DocProcKVPSchema, Assignment, Conditions, EndNodeSpec, Expression, ForeachPolicy, ForeachSpec, LoopSpec, BranchNodeSpec, MatchPolicy, NodeIdCondition, PlainTextReadingOrder, PromptExample, PromptLLMParameters, PromptNodeSpec, TimerNodeSpec,
29
29
  StartNodeSpec, ToolSpec, JsonSchemaObject, ToolRequestBody, ToolResponseBody, UserFieldKind, UserFieldOption, UserFlowSpec, UserNodeSpec, WaitPolicy,
30
30
  DocProcSpec, TextExtractionResponse, DocProcInput, DecisionsNodeSpec, DecisionsRule, DocExtSpec, File, DocumentClassificationResponse, DocClassifierSpec, DocumentProcessingCommonInput
31
31
  )
@@ -64,7 +64,7 @@ class FlowEdge(BaseModel):
64
64
 
65
65
  class Flow(Node):
66
66
  '''Flow represents a flow that will be run by wxO Flow engine.'''
67
- output_map: DataMap | None = None
67
+ output_map: dict[str, DataMap] | None = None
68
68
  nodes: dict[str, SerializeAsAny[Node]] = {}
69
69
  edges: List[FlowEdge] = []
70
70
  schemas: dict[str, JsonSchemaObject] = {}
@@ -401,6 +401,7 @@ class Flow(Node):
401
401
  display_name: str|None=None,
402
402
  system_prompt: str | list[str] | None = None,
403
403
  user_prompt: str | list[str] | None = None,
404
+ prompt_examples: list[PromptExample] | None = None,
404
405
  llm: str | None = None,
405
406
  llm_parameters: PromptLLMParameters | None = None,
406
407
  description: str | None = None,
@@ -422,6 +423,7 @@ class Flow(Node):
422
423
  description=description,
423
424
  system_prompt=system_prompt,
424
425
  user_prompt=user_prompt,
426
+ prompt_examples=prompt_examples,
425
427
  llm=llm,
426
428
  llm_parameters=llm_parameters,
427
429
  input_schema=_get_tool_request_body(input_schema_obj),
@@ -721,7 +723,7 @@ class Flow(Node):
721
723
  '''Create a single node flow with an automatic START and END node.'''
722
724
  return self.sequence(START, node, END)
723
725
 
724
- def branch(self, evaluator: Union[Callable, Expression]) -> "Branch":
726
+ def branch(self, evaluator: Union[Callable, Expression, Conditions]) -> 'Branch':
725
727
  '''Create a BRANCH node'''
726
728
  e = evaluator
727
729
  if isinstance(evaluator, Callable):
@@ -735,11 +737,19 @@ class Flow(Node):
735
737
  # e = new_script_spec
736
738
  elif isinstance(evaluator, str):
737
739
  e = Expression(expression=evaluator)
740
+ elif isinstance(evaluator, list):
741
+ e = Conditions(conditions=evaluator)
738
742
 
739
743
  spec = BranchNodeSpec(name = "branch_" + str(self._next_sequence_id()), evaluator=e)
740
744
  branch_node = Branch(spec = spec, containing_flow=self)
741
745
  return cast(Branch, self._add_node(branch_node))
742
746
 
747
+ def conditions(self) -> 'Branch':
748
+ '''Create a Branch node with empty Conditions evaluator (if-else)'''
749
+ spec = BranchNodeSpec(name = "branch_" + str(self._next_sequence_id()), evaluator=Conditions(conditions=[]))
750
+ branch_conditions_node = Branch(spec = spec, containing_flow=self)
751
+ return cast(Branch, self._add_node(branch_conditions_node))
752
+
743
753
  def wait_for(self, *args) -> "Wait":
744
754
  '''Wait for all incoming nodes to complete.'''
745
755
  raise ValueError("Not implemented yet.")
@@ -754,6 +764,77 @@ class Flow(Node):
754
764
 
755
765
  # return cast(Wait, self.node(wait_node))
756
766
 
767
+ def map_flow_output_with_variable(self, target_output_variable: str, variable: str, default_value: str = None) -> Self:
768
+ if self.output_map and "spec" in self.output_map:
769
+ maps = self.output_map["spec"].maps or []
770
+ else:
771
+ maps = []
772
+
773
+ curr_map_metadata = {
774
+ "assignmentType": "variable"
775
+ }
776
+
777
+ target_variable = "flow.output." + target_output_variable
778
+ value_expression = "flow." + variable
779
+
780
+ if default_value:
781
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, default_value=default_value, metadata=curr_map_metadata))
782
+ else:
783
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, metadata=curr_map_metadata))
784
+
785
+ flow_output_map_spec = DataMap(maps=maps)
786
+
787
+ if self.output_map and "spec" in self.output_map:
788
+ self.output_map["spec"] = flow_output_map_spec
789
+ else:
790
+ self.output_map = {"spec": flow_output_map_spec}
791
+ return self
792
+
793
+ def map_output(self, output_variable: str, expression: str, default_value: str = None) -> Self:
794
+ if self.output_map and "spec" in self.output_map:
795
+ maps = self.output_map["spec"].maps or []
796
+ else:
797
+ maps = []
798
+
799
+ curr_map_metadata = {
800
+ "assignmentType": "pyExpression"
801
+ }
802
+
803
+ target_variable = "flow.output." + output_variable
804
+ value_expression = expression
805
+
806
+ if default_value:
807
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, default_value=default_value, metadata=curr_map_metadata))
808
+ else:
809
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, metadata=curr_map_metadata))
810
+
811
+ flow_output_map_spec = DataMap(maps=maps)
812
+
813
+ if self.output_map and "spec" in self.output_map:
814
+ self.output_map["spec"] = flow_output_map_spec
815
+ else:
816
+ self.output_map = {"spec": flow_output_map_spec}
817
+ return self
818
+
819
+ def map_flow_output_with_none(self, target_output_variable: str) -> Self:
820
+ if self.output_map and "spec" in self.output_map:
821
+ maps = self.output_map["spec"].maps or []
822
+ else:
823
+ maps = []
824
+
825
+
826
+ target_variable = "flow.output." + target_output_variable
827
+
828
+ maps.append(Assignment(target_variable=target_variable, value_expression=None))
829
+
830
+ flow_output_map_spec = DataMap(maps=maps)
831
+
832
+ if self.output_map and "spec" in self.output_map:
833
+ self.output_map["spec"] = flow_output_map_spec
834
+ else:
835
+ self.output_map = {"spec": flow_output_map_spec}
836
+ return self
837
+
757
838
 
758
839
  def foreach(self, item_schema: type[BaseModel],
759
840
  input_schema: type[BaseModel] |None=None,
@@ -814,8 +895,6 @@ class Flow(Node):
814
895
  input_schema: type[BaseModel] |None=None,
815
896
  output_schema: type[BaseModel] |None=None) -> "UserFlow": # return a UserFlow object
816
897
 
817
- raise ValueError("userflow is NOT supported yet and it's interface will change.")
818
-
819
898
  output_schema_obj = _get_json_schema_obj("output", output_schema)
820
899
  input_schema_obj = _get_json_schema_obj("input", input_schema)
821
900
 
@@ -917,6 +996,11 @@ class Flow(Node):
917
996
  for key, value in self.metadata.items():
918
997
  metadata_dict[key] = value
919
998
  flow_dict["metadata"] = metadata_dict
999
+
1000
+ if self.output_map and "spec" in self.output_map:
1001
+ flow_dict["output_map"] = {
1002
+ "spec": self.output_map["spec"].to_json()
1003
+ }
920
1004
  return flow_dict
921
1005
 
922
1006
  def _get_node_id(self, node: Union[str, Node]) -> str:
@@ -1226,6 +1310,27 @@ class Branch(FlowControl):
1226
1310
  raise ValueError("Cannot have custom label __default__. Use default() instead.")
1227
1311
 
1228
1312
  return self._add_case(label, node)
1313
+
1314
+ def condition(self, to_node: Node, expression: str="", default: bool=False) -> Self:
1315
+ '''
1316
+ Add a condition to this branch node.
1317
+
1318
+ Parameters:
1319
+ expression (str): The expression of this condition.
1320
+ to_node (Node): The node to go to when expression is evaluated to true.
1321
+ default (bool): The condition is the default (else) case.
1322
+ '''
1323
+
1324
+ node_id = self.containing_flow._get_node_id(to_node)
1325
+ if default:
1326
+ condition = NodeIdCondition(node_id=node_id, default=default)
1327
+ else:
1328
+ condition = NodeIdCondition(expression=expression, node_id=node_id, default=default)
1329
+
1330
+ self.spec.evaluator.conditions.append(condition)
1331
+ self.containing_flow.edge(self, to_node)
1332
+
1333
+ return self
1229
1334
 
1230
1335
  def default(self, node: Node) -> Self:
1231
1336
  '''
@@ -1445,13 +1550,14 @@ class UserFlow(Flow):
1445
1550
  kind: UserFieldKind = UserFieldKind.Text,
1446
1551
  display_name: str | None = None,
1447
1552
  description: str | None = None,
1553
+ direction: str | None = None,
1448
1554
  default: Any | None = None,
1449
1555
  text: str = None, # The text used to ask question to the user, e.g. 'what is your name?'
1450
1556
  option: UserFieldOption | None = None,
1451
1557
  is_list: bool = False,
1452
1558
  min: Any | None = None,
1453
1559
  max: Any | None = None,
1454
- input_map: DataMap = None,
1560
+ input_map: DataMap | None= None,
1455
1561
  custom: dict[str, Any] = {}) -> UserNode:
1456
1562
  '''create a node in the flow'''
1457
1563
  # create a json schema object based on the single field
@@ -1483,6 +1589,8 @@ class UserFlow(Flow):
1483
1589
  description = description,
1484
1590
  default = default,
1485
1591
  text = text,
1592
+ direction = direction,
1593
+ input_map = input_map,
1486
1594
  option = option,
1487
1595
  is_list = is_list,
1488
1596
  min = min,
@@ -6,14 +6,14 @@ import yaml
6
6
  from pydantic import BaseModel, Field, SerializeAsAny, create_model
7
7
  from enum import Enum
8
8
 
9
- from .types import DocExtConfigField, EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, TimerNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec, DocProcSpec, \
9
+ from .types import Assignment, DocExtConfigField, EndNodeSpec, NodeSpec, AgentNodeSpec, PromptNodeSpec, TimerNodeSpec, StartNodeSpec, ToolNodeSpec, UserFieldKind, UserFieldOption, UserNodeSpec, DocProcSpec, \
10
10
  DocExtSpec, DocExtConfig, DocClassifierSpec, DecisionsNodeSpec, DocClassifierConfig
11
11
 
12
12
  from .data_map import DataMap
13
13
 
14
14
  class Node(BaseModel):
15
15
  spec: SerializeAsAny[NodeSpec]
16
- input_map: DataMap | None = None
16
+ input_map: dict[str, DataMap] | None = None
17
17
 
18
18
  def __call__(self, **kwargs):
19
19
  pass
@@ -40,10 +40,77 @@ class Node(BaseModel):
40
40
  def to_json(self) -> dict[str, Any]:
41
41
  model_spec = {}
42
42
  model_spec["spec"] = self.spec.to_json()
43
- if self.input_map is not None:
44
- model_spec['input_map'] = self.input_map.to_json()
43
+ if self.input_map is not None and "spec" in self.input_map:
44
+ model_spec['input_map'] = {
45
+ "spec": self.input_map["spec"].to_json()
46
+ }
45
47
 
46
48
  return model_spec
49
+
50
+ def map_node_input_with_variable(self, target_input_variable: str, variable: str, default_value: str = None) -> None:
51
+ if self.input_map and "spec" in self.input_map:
52
+ maps = self.input_map["spec"].maps or []
53
+ else:
54
+ maps = []
55
+
56
+ curr_map_metadata = {
57
+ "assignmentType": "variable"
58
+ }
59
+
60
+ target_variable = "self.input." + target_input_variable
61
+ value_expression = "flow." + variable
62
+
63
+ if default_value:
64
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, default_value=default_value, metadata=curr_map_metadata))
65
+ else:
66
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, metadata=curr_map_metadata))
67
+
68
+ node_input_map_spec = DataMap(maps=maps)
69
+ if self.input_map and "spec" in self.input_map:
70
+ self.input_map["spec"] = node_input_map_spec
71
+ else:
72
+ self.input_map = {"spec": node_input_map_spec}
73
+
74
+ def map_input(self, input_variable: str, expression: str, default_value: str = None) -> None:
75
+ if self.input_map and "spec" in self.input_map:
76
+ maps = self.input_map["spec"].maps or []
77
+ else:
78
+ maps = []
79
+
80
+ curr_map_metadata = {
81
+ "assignmentType": "pyExpression"
82
+ }
83
+
84
+ target_variable = "self.input." + input_variable
85
+ value_expression = expression
86
+
87
+ if default_value:
88
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, default_value=default_value, metadata=curr_map_metadata))
89
+ else:
90
+ maps.append(Assignment(target_variable=target_variable, value_expression=value_expression, metadata=curr_map_metadata))
91
+
92
+ node_input_map_spec = DataMap(maps=maps)
93
+ if self.input_map and "spec" in self.input_map:
94
+ self.input_map["spec"] = node_input_map_spec
95
+ else:
96
+ self.input_map = {"spec": node_input_map_spec}
97
+
98
+ def map_node_input_with_none(self, target_input_variable: str) -> None:
99
+ if self.input_map and "spec" in self.input_map:
100
+ maps = self.input_map["spec"].maps or []
101
+ else:
102
+ maps = []
103
+
104
+
105
+ target_variable = "self.input." + target_input_variable
106
+
107
+ maps.append(Assignment(target_variable=target_variable, value_expression=None))
108
+
109
+ node_input_map_spec = DataMap(maps=maps)
110
+ if self.input_map and "spec" in self.input_map:
111
+ self.input_map["spec"] = node_input_map_spec
112
+ else:
113
+ self.input_map = {"spec": node_input_map_spec}
47
114
 
48
115
  class StartNode(Node):
49
116
  def __repr__(self):
@@ -83,6 +150,8 @@ class UserNode(Node):
83
150
  option: UserFieldOption | None = None,
84
151
  min: Any | None = None,
85
152
  max: Any | None = None,
153
+ direction: str | None = None,
154
+ input_map: DataMap | None = None,
86
155
  is_list: bool = False,
87
156
  custom: dict[str, Any] | None = None,
88
157
  widget: str | None = None):
@@ -97,7 +166,9 @@ class UserNode(Node):
97
166
  max=max,
98
167
  is_list=is_list,
99
168
  custom=custom,
100
- widget=widget)
169
+ widget=widget,
170
+ direction=direction,
171
+ input_map=input_map)
101
172
 
102
173
  class AgentNode(Node):
103
174
  def __repr__(self):
@@ -28,6 +28,7 @@ from ibm_watsonx_orchestrate.agent_builder.tools.types import (
28
28
  )
29
29
  from .utils import get_valid_name
30
30
 
31
+
31
32
  logger = logging.getLogger(__name__)
32
33
 
33
34
  class JsonSchemaObjectRef(JsonSchemaObject):
@@ -360,7 +361,6 @@ class EndNodeSpec(NodeSpec):
360
361
  def __init__(self, **data):
361
362
  super().__init__(**data)
362
363
  self.kind = "end"
363
-
364
364
  class ToolNodeSpec(NodeSpec):
365
365
  tool: Union[str, ToolSpec] = Field(default = None, description="the tool to use")
366
366
 
@@ -421,9 +421,10 @@ class UserFieldKind(str, Enum):
421
421
  DateTime: str = "datetime"
422
422
  Time: str = "time"
423
423
  Number: str = "number"
424
- Document: str = "document"
424
+ File: str = "file"
425
425
  Boolean: str = "boolean"
426
426
  Object: str = "object"
427
+ Choice: str = "any"
427
428
 
428
429
  def convert_python_type_to_kind(python_type: type) -> "UserFieldKind":
429
430
  if inspect.isclass(python_type):
@@ -463,8 +464,8 @@ class UserFieldKind(str, Enum):
463
464
  model_spec["format"] = "number"
464
465
  elif kind == UserFieldKind.Boolean:
465
466
  model_spec["type"] = "boolean"
466
- elif kind == UserFieldKind.Document:
467
- model_spec["format"] = "uri"
467
+ elif kind == UserFieldKind.File:
468
+ model_spec["format"] = "wxo-file"
468
469
  elif kind == UserFieldKind.Object:
469
470
  raise ValueError("Object user fields are not supported.")
470
471
 
@@ -483,6 +484,8 @@ class UserField(BaseModel):
483
484
  text: str | None = Field(default=None, description="A descriptive text that can be used to ask user about this field.")
484
485
  display_name: str | None = None
485
486
  description: str | None = None
487
+ direction: str | None = None
488
+ input_map: Any | None = None,
486
489
  default: Any | None = None
487
490
  option: UserFieldOption | None = None
488
491
  min: Any | None = None,
@@ -497,6 +500,15 @@ class UserField(BaseModel):
497
500
  model_spec["name"] = self.name
498
501
  if self.kind:
499
502
  model_spec["kind"] = self.kind.value
503
+ if self.direction:
504
+ model_spec["direction"] = self.direction
505
+ if self.input_map:
506
+ # workaround for circular dependency related to Assigments in the Datamap module
507
+ from .data_map import DataMap
508
+ if self.input_map and not isinstance(self.input_map, DataMap):
509
+ raise TypeError("input_map must be an instance of DataMap")
510
+ #model_spec["input_map"] = self.input_map.to_json()
511
+ model_spec["input_map"] = {"spec": self.input_map.to_json()}
500
512
  if self.text:
501
513
  model_spec["text"] = self.text
502
514
  if self.display_name:
@@ -556,7 +568,15 @@ class UserNodeSpec(NodeSpec):
556
568
  max: Any | None = None,
557
569
  is_list: bool = False,
558
570
  custom: dict[str, Any] | None = None,
559
- widget: str | None = None):
571
+ widget: str | None = None,
572
+ input_map: Any | None = None,
573
+ direction: str | None = None):
574
+
575
+ # workaround for circular dependency related to Assigments in the Datamap module
576
+ from .data_map import DataMap
577
+ if input_map and not isinstance(input_map, DataMap):
578
+ raise TypeError("input_map must be an instance of DataMap")
579
+
560
580
  userfield = UserField(name=name,
561
581
  kind=kind,
562
582
  text=text,
@@ -568,7 +588,9 @@ class UserNodeSpec(NodeSpec):
568
588
  max=max,
569
589
  is_list=is_list,
570
590
  custom=custom,
571
- widget=widget)
591
+ widget=widget,
592
+ direction=direction,
593
+ input_map=input_map)
572
594
 
573
595
  # find the index of the field
574
596
  i = 0
@@ -660,11 +682,28 @@ class PromptLLMParameters(BaseModel):
660
682
  if self.stop_sequences:
661
683
  model_spec["stop_sequences"] = self.stop_sequences
662
684
  return model_spec
685
+
686
+ class PromptExample(BaseModel):
687
+ input: Optional[str] = None
688
+ expected_output: Optional[str] = None
689
+ enabled: bool
690
+
691
+ def to_json(self) -> dict[str, Any]:
692
+ model_spec = {}
693
+ if self.input:
694
+ model_spec["input"] = self.input
695
+ if self.expected_output:
696
+ model_spec["expected_output"] = self.expected_output
697
+ if self.enabled:
698
+ model_spec["enabled"] = self.enabled
699
+ return model_spec
700
+
663
701
 
664
702
 
665
703
  class PromptNodeSpec(NodeSpec):
666
704
  system_prompt: str | list[str]
667
705
  user_prompt: str | list[str]
706
+ prompt_examples: Optional[list[PromptExample]]
668
707
  llm: Optional[str]
669
708
  llm_parameters: Optional[PromptLLMParameters]
670
709
 
@@ -682,7 +721,10 @@ class PromptNodeSpec(NodeSpec):
682
721
  model_spec["llm"] = self.llm
683
722
  if self.llm_parameters:
684
723
  model_spec["llm_parameters"] = self.llm_parameters.to_json()
685
-
724
+ if self.prompt_examples:
725
+ model_spec["prompt_examples"] = []
726
+ for example in self.prompt_examples:
727
+ model_spec["prompt_examples"].append(example.to_json())
686
728
  return model_spec
687
729
 
688
730
  class TimerNodeSpec(NodeSpec):
@@ -707,6 +749,47 @@ class Expression(BaseModel):
707
749
  model_spec["expression"] = self.expression;
708
750
  return model_spec
709
751
 
752
+ class NodeIdCondition(BaseModel):
753
+ '''One Condition contains an expression, a node_id that branch should go to when expression is true, and a default indicator. '''
754
+ expression: Optional[str] = Field(description="A python expression to be run by the flow engine", default=None)
755
+ node_id: str = Field(description="ID of the node in the flow that branch node should go to")
756
+ default: bool = Field(description="Boolean indicating if the condition is default case")
757
+
758
+ def to_json(self) -> dict[str, Any]:
759
+ model_spec = {}
760
+ if self.expression:
761
+ model_spec["expression"] = self.expression
762
+ model_spec["node_id"] = self.node_id
763
+ model_spec["default"] = self.default
764
+ return model_spec
765
+
766
+
767
+ class EdgeIdCondition(BaseModel):
768
+ '''One Condition contains an expression, an edge_id that branch should go to when expression is true, and a default indicator. '''
769
+ expression: Optional[str] = Field(description="A python expression to be run by the flow engine")
770
+ edge_id: str = Field(description="ID of the edge in the flow that branch node should go to")
771
+ default: bool = Field(description="Boolean indicating if the condition is default case")
772
+
773
+ def to_json(self) -> dict[str, Any]:
774
+ model_spec = {}
775
+ if self.expression:
776
+ model_spec["expression"] = self.expression
777
+ model_spec["edge_id"] = self.edge_id
778
+ model_spec["default"] = self.default
779
+ return model_spec
780
+
781
+ class Conditions(BaseModel):
782
+ '''One Conditions is an array represents the if-else conditions of a complex branch'''
783
+ conditions: list = List[Union[NodeIdCondition, EdgeIdCondition]]
784
+
785
+ def to_json(self) -> dict[str, Any]:
786
+ model_spec = {}
787
+ condition_list = []
788
+ for condition in self.conditions:
789
+ condition_list.append(NodeIdCondition.model_validate(condition).to_json())
790
+ model_spec["conditions"] = condition_list
791
+ return model_spec
792
+
710
793
  class MatchPolicy(Enum):
711
794
 
712
795
  FIRST_MATCH = 1
@@ -724,7 +807,7 @@ class BranchNodeSpec(FlowControlNodeSpec):
724
807
  cases (dict[str | bool, str]): A dictionary of labels to node names. The keys can be strings or booleans.
725
808
  match_policy (MatchPolicy): The policy to use when evaluating the expression.
726
809
  '''
727
- evaluator: Expression
810
+ evaluator: Expression | Conditions
728
811
  cases: dict[str | bool, str] = Field(default = {},
729
812
  description="A dictionary of labels to node names.")
730
813
  match_policy: MatchPolicy = Field(default = MatchPolicy.FIRST_MATCH)
@@ -808,7 +891,7 @@ class UserFlowSpec(FlowSpec):
808
891
 
809
892
  def __init__(self, **kwargs):
810
893
  super().__init__(**kwargs)
811
- self.kind = "userflow"
894
+ self.kind = "user_flow"
812
895
 
813
896
  def to_json(self) -> dict[str, Any]:
814
897
  model_spec = super().to_json()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibm-watsonx-orchestrate
3
- Version: 1.10.0b1
3
+ Version: 1.10.1
4
4
  Summary: IBM watsonx.orchestrate SDK
5
5
  Author-email: IBM <support@ibm.com>
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- ibm_watsonx_orchestrate/__init__.py,sha256=G8rgpNWoKMo_SVWHA47iL6iuMrXwKIw9aaWYcFRb-v0,429
1
+ ibm_watsonx_orchestrate/__init__.py,sha256=PRy6NfSG4mSt4yawB2bLg9gwssVrWTF8ssncV9sp0vU,426
2
2
  ibm_watsonx_orchestrate/agent_builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  ibm_watsonx_orchestrate/agent_builder/agents/__init__.py,sha256=lmZwaiWXD4Ea19nrMwZXaqCxFMG29xNS8vUoZtK3yI4,392
4
4
  ibm_watsonx_orchestrate/agent_builder/agents/agent.py,sha256=W0uya81fQPrYZFaO_tlsxBL56Bfpw0xrqdxQJhAZ6XI,983
@@ -9,8 +9,8 @@ ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py,
9
9
  ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py,sha256=jNVF_jgz1Dmt7-RxAceAS0XWXk_fx9h3sS_fGrvZT28,941
10
10
  ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py,sha256=U76wZrblSXx4qv7phcPYs3l8SiFzwZ5cJ74u8Y2iYhU,608
11
11
  ibm_watsonx_orchestrate/agent_builder/connections/__init__.py,sha256=B9FwmBbdJxFj3KmJ87Fc78TbzxOr1MIVhaHPJePOGSQ,716
12
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py,sha256=Vo1ucn4c_Db68x7GGuq-uyM89dInnkRztmsF0uo-yBk,5421
13
- ibm_watsonx_orchestrate/agent_builder/connections/types.py,sha256=OGuFEQhc1dj8GVjHuT_V3coCC_YtBGqQ68qGy96m6EQ,9587
12
+ ibm_watsonx_orchestrate/agent_builder/connections/connections.py,sha256=ozRTZ1Y10YbipE6oegI8QIP31fWqUaBUFoHAo-WTY3g,5547
13
+ ibm_watsonx_orchestrate/agent_builder/connections/types.py,sha256=cfM2L4VscC-0muDwqq5EhCKKQxwWyHmA-rQBE9LE7r0,11244
14
14
  ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py,sha256=_KuGF0RZpKpwdt31rzjlTjrhGRFz2RtLzleNkhMNX4k,1831
15
15
  ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py,sha256=3xTfFMZR17EN8eYRhsVyBfOEzlTqyi0eYaMXyv0_ZtQ,862
16
16
  ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py,sha256=2MP3_c6V3OdbwrdDDffGtrBoAhoa3q-YoUXj_RsMG_M,8340
@@ -42,8 +42,8 @@ ibm_watsonx_orchestrate/cli/commands/channels/types.py,sha256=hMFvWPr7tAmDrhBqtz
42
42
  ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_command.py,sha256=vPCr6z1n1yzGDjTlM4f3_9MiuVRzNmXbvUifm6XyQi8,1103
43
43
  ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py,sha256=CGfmKsCBX4E3HMZ8C0IXD-DHQNwe96V1Y_BxUZM2us0,8557
44
44
  ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py,sha256=Q9vg2Z5Fsunu6GQFY_TIsNRhUCa0SSGSPnK4jxSGK34,1581
45
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py,sha256=bYn9Z4DDDdoaAYkUddgrQas8H7h7h7hbO4fle5Ovdmo,10688
46
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py,sha256=XpcJBAzJHczZcJttH7kzfIgZUidj_aW86mmQ5qk_T8c,23367
45
+ ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py,sha256=lwPhDPemDvFU-j7QUj8E07hsnzLBUJDNcyhQDzxs3S8,12442
46
+ ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py,sha256=_i1gaR2Nuy9gPLJZTJWoVnRvg9vKcwyg2UoiK5Azsw4,26390
47
47
  ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py,sha256=IxasApIyQYWRMKPXKa38ZPVkUvOc4chggSmSGjgQGXc,2345
48
48
  ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py,sha256=SC2Tjq6r-tHIiyPBMajsxdMIY3BQpRWpkYGZS2XbJyU,18981
49
49
  ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py,sha256=AcBE97qNYsRN0ftY_E-AAjKFyVea4fHtU5eB2HsB42I,5619
@@ -58,8 +58,8 @@ ibm_watsonx_orchestrate/cli/commands/login/login_command.py,sha256=xArMiojoozg7E
58
58
  ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py,sha256=mbvBR5o9M7W6OpTZyd6TtSEOIXq07dPYz4hv5zDrsd0,8129
59
59
  ibm_watsonx_orchestrate/cli/commands/models/models_command.py,sha256=PW-PIM5Nq0qdCopWjANGBWEuEoA3NLTFThYrN8ggGCI,6425
60
60
  ibm_watsonx_orchestrate/cli/commands/models/models_controller.py,sha256=eZSYQUg9TL_-8lgcPVpKIx7MtOE7K_NCvZW9Y9YsFA0,18466
61
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py,sha256=RBGH3ssDDhrcvmGpvpVtvqpWIhJm_odH4kqf4UQMRrI,46669
62
- ibm_watsonx_orchestrate/cli/commands/server/types.py,sha256=UCrgGErbSVBnOzesfjrIii4tTCuVfWemYz5AKGZX0oA,4213
61
+ ibm_watsonx_orchestrate/cli/commands/server/server_command.py,sha256=cMy_GGYXbug5A6IGoddUzBUCmunySCI8BC-ebRL6p4A,46489
62
+ ibm_watsonx_orchestrate/cli/commands/server/types.py,sha256=lOrPLzadXJ3pVnIVsQRb0keXckFBGMDD3Holjfge9GI,4412
63
63
  ibm_watsonx_orchestrate/cli/commands/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
64
  ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py,sha256=CzXRkd-97jXyS6LtaaNtMah-aZu0919dYl-mDwzGThc,344
65
65
  ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -101,23 +101,23 @@ ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py,sha256=TLFNS39EeBD_t4Y-
101
101
  ibm_watsonx_orchestrate/client/tools/tempus_client.py,sha256=24fKDZUOVHBW-Vj4mubnpnUmab5LgGn8u5hOVyJaozI,1804
102
102
  ibm_watsonx_orchestrate/client/tools/tool_client.py,sha256=d3i3alVwa0TCN72w9sWOrM20GCbNmnpTnqEOJVbBIFM,1994
103
103
  ibm_watsonx_orchestrate/client/voice_configurations/voice_configurations_client.py,sha256=M5xIPLiVNpP-zxQw8CTNT9AiBjeXXmJiNaE142e2A3E,2682
104
- ibm_watsonx_orchestrate/docker/compose-lite.yml,sha256=wylLRS6iGEfMitfoXuT2tVuSiWej0HLAE5k-6Onl-kI,45671
105
- ibm_watsonx_orchestrate/docker/default.env,sha256=ol3f-UGtJ0i2sTiwmEQPNSNzOe_VBNFc6zsgR0dC3XY,6268
104
+ ibm_watsonx_orchestrate/docker/compose-lite.yml,sha256=iFk3JcGUs4aanAzTZkgG1EcsunWp_awkuIphpjLmeX8,45848
105
+ ibm_watsonx_orchestrate/docker/default.env,sha256=jz6MNLTvVIVTUOY8AYLOLy589nMs_v0nzQM5rKYACwg,6293
106
106
  ibm_watsonx_orchestrate/docker/proxy-config-single.yaml,sha256=WEbK4ENFuTCYhzRu_QblWp1_GMARgZnx5vReQafkIG8,308
107
107
  ibm_watsonx_orchestrate/docker/start-up.sh,sha256=LTtwHp0AidVgjohis2LXGvZnkFQStOiUAxgGABOyeUI,1811
108
108
  ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl,sha256=Hi3-owh5OM0Jz2ihX9nLoojnr7Ky1TV-GelyqLcewLE,2047417
109
109
  ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz,sha256=e5T-q7XPAtiCyQljwZp6kk3Q_4Tg6y5sijHTkscmqqQ,2025466
110
110
  ibm_watsonx_orchestrate/docker/tempus/common-config.yaml,sha256=Zo3F36F5DV4VO_vUg1RG-r4WhcukVh79J2fXhGl6j0A,22
111
111
  ibm_watsonx_orchestrate/flow_builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
- ibm_watsonx_orchestrate/flow_builder/data_map.py,sha256=1brmWWFERDsNG2XGais-5-r58LKUUwBtqwdaLQIFRhE,503
113
- ibm_watsonx_orchestrate/flow_builder/node.py,sha256=2b4BL64tt1isvA5O_uFvXZG5qqvDYrAVSYyeW6wyk0U,6978
114
- ibm_watsonx_orchestrate/flow_builder/types.py,sha256=ohtfMFneFtK7KOEEEdYpD5hmiRwfyTnkHLkOr4zNhGs,47611
112
+ ibm_watsonx_orchestrate/flow_builder/data_map.py,sha256=LinePFgb5mBnrvNmPkFe3rq5oYJZSjcgmaEGpE6dVwc,586
113
+ ibm_watsonx_orchestrate/flow_builder/node.py,sha256=U7-cYYhe7Gj_j6U0dw1ufaPROvXmFo30ZI8s7eTXZlk,9940
114
+ ibm_watsonx_orchestrate/flow_builder/types.py,sha256=FJw9pvXag9paNtwH1p9pnva0bvLMJ-ISFemyDJe7WXY,51392
115
115
  ibm_watsonx_orchestrate/flow_builder/utils.py,sha256=8q1jr5i_TzoJpoQxmLiO0g5Uv03BLbTUaRfw8_0VWIY,11931
116
116
  ibm_watsonx_orchestrate/flow_builder/flows/__init__.py,sha256=iRYV0_eXgBBGhuNnvg-mUyPUyCIw5BiallPOp27bzYM,1083
117
117
  ibm_watsonx_orchestrate/flow_builder/flows/constants.py,sha256=-TGneZyjA4YiAtJJK7OmmjDHYQC4mw2e98MPAZqiB50,324
118
118
  ibm_watsonx_orchestrate/flow_builder/flows/decorators.py,sha256=lr4qSWq5PWqlGFf4fzUQZCVQDHBYflrYwZ24S89Aq80,2794
119
119
  ibm_watsonx_orchestrate/flow_builder/flows/events.py,sha256=VyaBm0sADwr15LWfKbcBQS1M80NKqzYDj3UlW3OpOf4,2984
120
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py,sha256=P-MazfKiQr8BLiFAqOxqKlFHM2KyY7Dgp8O6xdCxIjk,60388
120
+ ibm_watsonx_orchestrate/flow_builder/flows/flow.py,sha256=UqFgGxYM9EIXk6pZjMMI5s6r1C11Gb9kRN5NVbJChoc,64824
121
121
  ibm_watsonx_orchestrate/run/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
122
  ibm_watsonx_orchestrate/run/connections.py,sha256=9twXkNeUx83fP_qYUbJRtumE-wzPPNN2v-IY_8hGndM,1820
123
123
  ibm_watsonx_orchestrate/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -126,8 +126,8 @@ ibm_watsonx_orchestrate/utils/utils.py,sha256=U7z_2iASoFiZ2zM0a_2Mc2Y-P5oOT7hOwu
126
126
  ibm_watsonx_orchestrate/utils/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
127
  ibm_watsonx_orchestrate/utils/logging/logger.py,sha256=FzeGnidXAjC7yHrvIaj4KZPeaBBSCniZFlwgr5yV3oA,1037
128
128
  ibm_watsonx_orchestrate/utils/logging/logging.yaml,sha256=9_TKfuFr1barnOKP0fZT5D6MhddiwsXVTFjtRbcOO5w,314
129
- ibm_watsonx_orchestrate-1.10.0b1.dist-info/METADATA,sha256=Md6fKs7y3oZCmVgOR6jVw5eSF31wwRMtXSn1w0SFvN8,1363
130
- ibm_watsonx_orchestrate-1.10.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
131
- ibm_watsonx_orchestrate-1.10.0b1.dist-info/entry_points.txt,sha256=SfIT02-Jen5e99OcLhzbcM9Bdyf8SGVOCtnSplgZdQI,69
132
- ibm_watsonx_orchestrate-1.10.0b1.dist-info/licenses/LICENSE,sha256=Shgxx7hTdCOkiVRmfGgp_1ISISrwQD7m2f0y8Hsapl4,1083
133
- ibm_watsonx_orchestrate-1.10.0b1.dist-info/RECORD,,
129
+ ibm_watsonx_orchestrate-1.10.1.dist-info/METADATA,sha256=a2z_4K9IvBH1Elg31ecfkyAuPEIS4j5mHYn29boz2eg,1361
130
+ ibm_watsonx_orchestrate-1.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
131
+ ibm_watsonx_orchestrate-1.10.1.dist-info/entry_points.txt,sha256=SfIT02-Jen5e99OcLhzbcM9Bdyf8SGVOCtnSplgZdQI,69
132
+ ibm_watsonx_orchestrate-1.10.1.dist-info/licenses/LICENSE,sha256=Shgxx7hTdCOkiVRmfGgp_1ISISrwQD7m2f0y8Hsapl4,1083
133
+ ibm_watsonx_orchestrate-1.10.1.dist-info/RECORD,,