ibm-watsonx-orchestrate 1.9.0b2__py3-none-any.whl → 1.10.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 (39) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +2 -0
  3. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +1 -1
  4. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +1 -1
  5. ibm_watsonx_orchestrate/agent_builder/connections/types.py +16 -12
  6. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +47 -3
  7. ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +18 -15
  8. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +19 -7
  9. ibm_watsonx_orchestrate/agent_builder/tools/types.py +1 -1
  10. ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -0
  11. ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +98 -0
  12. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +20 -0
  13. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +170 -1
  14. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +7 -7
  15. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +36 -26
  16. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +51 -22
  17. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +110 -16
  18. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +43 -10
  19. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +52 -25
  20. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +5 -0
  21. ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_command.py +58 -0
  22. ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +173 -0
  23. ibm_watsonx_orchestrate/cli/main.py +2 -0
  24. ibm_watsonx_orchestrate/client/agents/agent_client.py +64 -1
  25. ibm_watsonx_orchestrate/client/connections/connections_client.py +4 -3
  26. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +4 -4
  27. ibm_watsonx_orchestrate/client/voice_configurations/voice_configurations_client.py +75 -0
  28. ibm_watsonx_orchestrate/docker/compose-lite.yml +54 -5
  29. ibm_watsonx_orchestrate/docker/default.env +21 -13
  30. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +2 -0
  31. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +115 -31
  32. ibm_watsonx_orchestrate/flow_builder/node.py +39 -15
  33. ibm_watsonx_orchestrate/flow_builder/types.py +114 -25
  34. ibm_watsonx_orchestrate/run/connections.py +2 -2
  35. {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/METADATA +1 -1
  36. {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/RECORD +39 -34
  37. {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/WHEEL +0 -0
  38. {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/entry_points.txt +0 -0
  39. {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -29,13 +29,17 @@ from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient, Agen
29
29
  from ibm_watsonx_orchestrate.client.agents.external_agent_client import ExternalAgentClient
30
30
  from ibm_watsonx_orchestrate.client.agents.assistant_agent_client import AssistantAgentClient
31
31
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
32
+ from ibm_watsonx_orchestrate.client.voice_configurations.voice_configurations_client import VoiceConfigurationsClient
32
33
  from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
33
34
  from ibm_watsonx_orchestrate.client.connections import get_connections_client
34
35
  from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
35
36
 
36
- from ibm_watsonx_orchestrate.client.utils import instantiate_client
37
+ from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
37
38
  from ibm_watsonx_orchestrate.utils.utils import check_file_in_zip
38
39
 
40
+ from rich.console import Console
41
+ from rich.progress import Progress, SpinnerColumn, TextColumn
42
+
39
43
  logger = logging.getLogger(__name__)
40
44
 
41
45
  # Helper generic type for any agent
@@ -197,6 +201,7 @@ def get_app_id_from_conn_id(conn_id: str) -> str:
197
201
  exit(1)
198
202
  return app_id
199
203
 
204
+
200
205
  def get_agent_details(name: str, client: AgentClient | ExternalAgentClient | AssistantAgentClient) -> dict:
201
206
  agent_specs = client.get_draft_by_name(name)
202
207
  if len(agent_specs) > 1:
@@ -219,6 +224,7 @@ class AgentsController:
219
224
  self.assistant_client = None
220
225
  self.tool_client = None
221
226
  self.knowledge_base_client = None
227
+ self.voice_configuration_client = None
222
228
 
223
229
  def get_native_client(self):
224
230
  if not self.native_client:
@@ -245,6 +251,11 @@ class AgentsController:
245
251
  self.knowledge_base_client = instantiate_client(KnowledgeBaseClient)
246
252
  return self.knowledge_base_client
247
253
 
254
+ def get_voice_configuration_client(self):
255
+ if not self.voice_configuration_client:
256
+ self.voice_configuration_client = instantiate_client(VoiceConfigurationsClient)
257
+ return self.voice_configuration_client
258
+
248
259
  @staticmethod
249
260
  def import_agent(file: str, app_id: str) -> List[Agent | ExternalAgent | AssistantAgent]:
250
261
  agents = parse_file(file)
@@ -520,6 +531,38 @@ class AgentsController:
520
531
  guideline.tool = name
521
532
 
522
533
  return ref_agent
534
+
535
+ def get_voice_config_name_from_id(self, voice_config_id: str) -> str | None:
536
+ client = self.get_voice_configuration_client()
537
+ config = client.get_by_id(voice_config_id)
538
+ return config.name if config else None
539
+
540
+ def get_voice_config_id_from_name(self, voice_config_name: str) -> str | None:
541
+ client = self.get_voice_configuration_client()
542
+ configs = client.get_by_name(voice_config_name)
543
+
544
+ if len(configs) == 0:
545
+ logger.error(f"No voice_configs with the name '{voice_config_name}' found. Failed to get config")
546
+ sys.exit(1)
547
+
548
+ if len(configs) > 1:
549
+ logger.error(f"Multiple voice_configs with the name '{voice_config_name}' found. Failed to get config")
550
+ sys.exit(1)
551
+
552
+ return configs[0].voice_configuration_id
553
+
554
+
555
+ def reference_voice_config(self,agent: Agent):
556
+ deref_agent = deepcopy(agent)
557
+ deref_agent.voice_configuration = self.get_voice_config_name_from_id(agent.voice_configuration_id)
558
+ del deref_agent.voice_configuration_id
559
+ return deref_agent
560
+
561
+ def dereference_voice_config(self,agent: Agent):
562
+ ref_agent = deepcopy(agent)
563
+ ref_agent.voice_configuration_id = self.get_voice_config_id_from_name(agent.voice_configuration)
564
+ del ref_agent.voice_configuration
565
+ return ref_agent
523
566
 
524
567
  @staticmethod
525
568
  def dereference_app_id(agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
@@ -540,7 +583,18 @@ class AgentsController:
540
583
  agent.config.connection_id = None
541
584
 
542
585
  return agent
586
+
587
+ def dereference_common_agent_dependencies(self, agent: AnyAgentT) -> AnyAgentT:
588
+ if agent.voice_configuration:
589
+ agent = self.dereference_voice_config(agent)
590
+
591
+ return agent
592
+
593
+ def reference_common_agent_dependencies(self, agent: AnyAgentT) -> AnyAgentT:
594
+ if agent.voice_configuration_id:
595
+ agent = self.reference_voice_config(agent)
543
596
 
597
+ return agent
544
598
 
545
599
  def dereference_native_agent_dependencies(self, agent: Agent) -> Agent:
546
600
  if agent.collaborators and len(agent.collaborators):
@@ -584,6 +638,8 @@ class AgentsController:
584
638
 
585
639
  # Convert all names used in an agent to the corresponding ids
586
640
  def dereference_agent_dependencies(self, agent: AnyAgentT) -> AnyAgentT:
641
+
642
+ agent = self.dereference_common_agent_dependencies(agent)
587
643
  if isinstance(agent, Agent):
588
644
  return self.dereference_native_agent_dependencies(agent)
589
645
  if isinstance(agent, ExternalAgent) or isinstance(agent, AssistantAgent):
@@ -591,6 +647,8 @@ class AgentsController:
591
647
 
592
648
  # Convert all ids used in an agent to the corresponding names
593
649
  def reference_agent_dependencies(self, agent: AnyAgentT) -> AnyAgentT:
650
+
651
+ agent = self.reference_common_agent_dependencies(agent)
594
652
  if isinstance(agent, Agent):
595
653
  return self.reference_native_agent_dependencies(agent)
596
654
  if isinstance(agent, ExternalAgent) or isinstance(agent, AssistantAgent):
@@ -1111,3 +1169,114 @@ class AgentsController:
1111
1169
  logger.info(f"Successfully wrote agents and tools to '{output_path}'")
1112
1170
  zip_file_out.close()
1113
1171
 
1172
+
1173
+ def deploy_agent(self, name: str):
1174
+ if is_local_dev():
1175
+ logger.error("Agents cannot be deployed in Developer Edition")
1176
+ sys.exit(1)
1177
+ native_client = self.get_native_client()
1178
+ external_client = self.get_external_client()
1179
+ assistant_client = self.get_assistant_client()
1180
+
1181
+ existing_native_agents = native_client.get_draft_by_name(name)
1182
+ existing_external_agents = external_client.get_draft_by_name(name)
1183
+ existing_assistant_agents = assistant_client.get_draft_by_name(name)
1184
+
1185
+ if len(existing_native_agents) == 0 and (len(existing_external_agents) >= 1 or len(existing_assistant_agents) >= 1):
1186
+ logger.error(f"No native agent found with name '{name}'. Only Native Agents can be deployed to a Live Environment")
1187
+ sys.exit(1)
1188
+ if len(existing_native_agents) > 1:
1189
+ logger.error(f"Multiple native agents with the name '{name}' found. Failed to get agent")
1190
+ sys.exit(1)
1191
+ if len(existing_native_agents) == 0:
1192
+ logger.error(f"No native agents with the name '{name}' found. Failed to get agent")
1193
+ sys.exit(1)
1194
+
1195
+
1196
+ agent_details = existing_native_agents[0]
1197
+ agent_id = agent_details.get("id")
1198
+
1199
+ environments = native_client.get_environments_for_agent(agent_id)
1200
+
1201
+ live_environment = [env for env in environments if env.get("name") == "live"]
1202
+ if live_environment is None:
1203
+ logger.error("No live environment found for this tenant")
1204
+ sys.exit(1)
1205
+
1206
+ live_env_id = live_environment[0].get("id")
1207
+
1208
+ console = Console()
1209
+ with Progress(
1210
+ SpinnerColumn(spinner_name="dots"),
1211
+ TextColumn("[progress.description]{task.description}"),
1212
+ transient=True,
1213
+ console=console,
1214
+ ) as progress:
1215
+ progress.add_task(description="Deploying agent to Live envrionment", total=None)
1216
+
1217
+ status = native_client.deploy(agent_id, live_env_id)
1218
+
1219
+ if status:
1220
+ logger.info(f"Successfully deployed agent {name}")
1221
+ else:
1222
+ logger.error(f"Error deploying agent {name}")
1223
+
1224
+ def undeploy_agent(self, name: str):
1225
+ if is_local_dev():
1226
+ logger.error("Agents cannot be undeployed in Developer Edition")
1227
+ sys.exit(1)
1228
+
1229
+ native_client = self.get_native_client()
1230
+ external_client = self.get_external_client()
1231
+ assistant_client = self.get_assistant_client()
1232
+
1233
+ existing_native_agents = native_client.get_draft_by_name(name)
1234
+ existing_external_agents = external_client.get_draft_by_name(name)
1235
+ existing_assistant_agents = assistant_client.get_draft_by_name(name)
1236
+
1237
+ if len(existing_native_agents) == 0 and (len(existing_external_agents) >= 1 or len(existing_assistant_agents) >= 1):
1238
+ logger.error(f"No native agent found with name '{name}'. Only Native Agents can be undeployed from a Live Environment")
1239
+ sys.exit(1)
1240
+ if len(existing_native_agents) > 1:
1241
+ logger.error(f"Multiple native agents with the name '{name}' found. Failed to get agent")
1242
+ sys.exit(1)
1243
+ if len(existing_native_agents) == 0:
1244
+ logger.error(f"No native agents with the name '{name}' found. Failed to get agent")
1245
+ sys.exit(1)
1246
+
1247
+ agent_details = existing_native_agents[0]
1248
+ agent_id = agent_details.get("id")
1249
+
1250
+ environments = native_client.get_environments_for_agent(agent_id)
1251
+ live_environment = [env for env in environments if env.get("name") == "live"]
1252
+ if live_environment is None:
1253
+ logger.error("No live environment found for this tenant")
1254
+ sys.exit(1)
1255
+ version_id = live_environment[0].get("current_version")
1256
+
1257
+ if version_id is None:
1258
+ agent_name = agent_details.get("name")
1259
+ logger.error(f"Agent {agent_name} does not exist in a Live environment")
1260
+ sys.exit(1)
1261
+
1262
+ draft_environment = [env for env in environments if env.get("name") == "draft"]
1263
+ if draft_environment is None:
1264
+ logger.error("No draft environment found for this tenant")
1265
+ sys.exit(1)
1266
+ draft_env_id = draft_environment[0].get("id")
1267
+
1268
+ console = Console()
1269
+ with Progress(
1270
+ SpinnerColumn(spinner_name="dots"),
1271
+ TextColumn("[progress.description]{task.description}"),
1272
+ transient=True,
1273
+ console=console,
1274
+ ) as progress:
1275
+ progress.add_task(description="Undeploying agent to Draft envrionment", total=None)
1276
+
1277
+ status = native_client.undeploy(agent_id, version_id, draft_env_id)
1278
+ if status:
1279
+ logger.info(f"Successfully undeployed agent {name}")
1280
+ else:
1281
+ logger.error(f"Error undeploying agent {name}")
1282
+
@@ -160,7 +160,7 @@ def set_credentials_connection_command(
160
160
  typer.Option(
161
161
  '--username',
162
162
  '-u',
163
- help='For basic auth, the username to login with'
163
+ help='For basic auth and oauth_auth_password_flow, the username to login with'
164
164
  )
165
165
  ] = None,
166
166
  password: Annotated[
@@ -168,7 +168,7 @@ def set_credentials_connection_command(
168
168
  typer.Option(
169
169
  '--password',
170
170
  '-p',
171
- help='For basic auth, the password to login with'
171
+ help='For basic auth and oauth_auth_password_flow, the password to login with'
172
172
  )
173
173
  ] = None,
174
174
  token: Annotated[
@@ -191,14 +191,14 @@ def set_credentials_connection_command(
191
191
  typer.Option(
192
192
  '--client-id',
193
193
  # help='For oauth_auth_on_behalf_of_flow, oauth_auth_code_flow, oauth_auth_implicit_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the client_id to authenticate against the application token server'
194
- help='For oauth_auth_on_behalf_of_flow and oauth_auth_client_credentials_flow, the client_id to authenticate against the application token server'
194
+ help='For oauth_auth_on_behalf_of_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the client_id to authenticate against the application token server'
195
195
  )
196
196
  ] = None,
197
197
  client_secret: Annotated[
198
198
  str,
199
199
  typer.Option(
200
200
  '--client-secret',
201
- help='For oauth_auth_client_credentials_flow, the client_secret to authenticate with'
201
+ help='For oauth_auth_client_credentials_flow and oauth_auth_password_flow, the client_secret to authenticate with'
202
202
  )
203
203
  ] = None,
204
204
  send_via: Annotated[
@@ -213,7 +213,7 @@ def set_credentials_connection_command(
213
213
  typer.Option(
214
214
  '--token-url',
215
215
  # help='For oauth_auth_on_behalf_of_flow, oauth_auth_code_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the url of the application token server'
216
- help='For oauth_auth_on_behalf_of_flow and oauth_auth_client_credentials_flow, the url of the application token server'
216
+ help='For oauth_auth_on_behalf_of_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the url of the application token server'
217
217
  )
218
218
  ] = None,
219
219
  auth_url: Annotated[
@@ -227,14 +227,14 @@ def set_credentials_connection_command(
227
227
  str,
228
228
  typer.Option(
229
229
  '--grant-type',
230
- help='For oauth_auth_on_behalf_of_flow and oauth_auth_client_credentials_flow, the grant type used by the application token server'
230
+ help='For oauth_auth_on_behalf_of_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the grant type used by the application token server'
231
231
  )
232
232
  ] = None,
233
233
  scope: Annotated[
234
234
  str,
235
235
  typer.Option(
236
236
  '--scope',
237
- help='For oauth_auth_code_flow and oauth_auth_client_credentials_flow, the optional scopes used by the application token server. Should be in the form of a space seperated string.'
237
+ help='For oauth_auth_code_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the optional scopes used by the application token server. Should be in the form of a space seperated string.'
238
238
  )
239
239
  ] = None,
240
240
  entries: Annotated[
@@ -19,15 +19,15 @@ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
19
19
  BasicAuthCredentials,
20
20
  BearerTokenAuthCredentials,
21
21
  APIKeyAuthCredentials,
22
- # OAuth2AuthCodeCredentials,
22
+ OAuth2AuthCodeCredentials,
23
23
  OAuth2ClientCredentials,
24
24
  # OAuth2ImplicitCredentials,
25
- # OAuth2PasswordCredentials,
25
+ OAuth2PasswordCredentials,
26
26
  OAuthOnBehalfOfCredentials,
27
27
  KeyValueConnectionCredentials,
28
28
  CREDENTIALS,
29
29
  IdentityProviderCredentials,
30
- OAUTH_CONNECTION_TYPES, OAuth2AuthCodeCredentials
30
+ OAUTH_CONNECTION_TYPES
31
31
 
32
32
  )
33
33
 
@@ -115,7 +115,7 @@ def _format_token_headers(header_list: List) -> dict:
115
115
 
116
116
  def _validate_connection_params(type: ConnectionType, **args) -> None:
117
117
 
118
- if type == ConnectionType.BASIC_AUTH and (
118
+ if type in {ConnectionType.BASIC_AUTH, ConnectionType.OAUTH2_PASSWORD} and (
119
119
  args.get('username') is None or args.get('password') is None
120
120
  ):
121
121
  raise typer.BadParameter(
@@ -136,7 +136,7 @@ def _validate_connection_params(type: ConnectionType, **args) -> None:
136
136
  f"Missing flags --api-key is required for type {type}"
137
137
  )
138
138
 
139
- if type in {ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and args.get('client_secret') is None:
139
+ if type in {ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE, ConnectionType.OAUTH2_PASSWORD} and args.get('client_secret') is None:
140
140
  raise typer.BadParameter(
141
141
  f"Missing flags --client-secret is required for type {type}"
142
142
  )
@@ -146,14 +146,14 @@ def _validate_connection_params(type: ConnectionType, **args) -> None:
146
146
  f"Missing flags --auth-url is required for type {type}"
147
147
  )
148
148
 
149
- if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and (
149
+ if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE, ConnectionType.OAUTH2_PASSWORD} and (
150
150
  args.get('client_id') is None
151
151
  ):
152
152
  raise typer.BadParameter(
153
153
  f"Missing flags --client-id is required for type {type}"
154
154
  )
155
155
 
156
- if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and (
156
+ if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE, ConnectionType.OAUTH2_PASSWORD} and (
157
157
  args.get('token_url') is None
158
158
  ):
159
159
  raise typer.BadParameter(
@@ -209,13 +209,11 @@ def _get_credentials(type: ConnectionType, **kwargs):
209
209
  # authorization_url=kwargs.get("auth_url"),
210
210
  # client_id=kwargs.get("client_id"),
211
211
  # )
212
- # case ConnectionType.OAUTH2_PASSWORD:
213
- # return OAuth2PasswordCredentials(
214
- # authorization_url=kwargs.get("auth_url"),
215
- # client_id=kwargs.get("client_id"),
216
- # client_secret=kwargs.get("client_secret"),
217
- # token_url=kwargs.get("token_url")
218
- # )
212
+ case ConnectionType.OAUTH2_PASSWORD:
213
+ keys = ["username", "password", "client_id","client_secret","token_url","grant_type", "scope"]
214
+ filtered_args = { key_name: kwargs[key_name] for key_name in keys if kwargs.get(key_name) }
215
+ return OAuth2PasswordCredentials(**filtered_args)
216
+
219
217
  case ConnectionType.OAUTH_ON_BEHALF_OF_FLOW:
220
218
  return OAuthOnBehalfOfCredentials(
221
219
  client_id=kwargs.get("client_id"),
@@ -283,25 +281,24 @@ def add_configuration(config: ConnectionConfiguration) -> None:
283
281
  logger.error(response_text)
284
282
  exit(1)
285
283
 
286
- def add_credentials(app_id: str, environment: ConnectionEnvironment, use_app_credentials: bool, credentials: CREDENTIALS) -> None:
284
+ def add_credentials(app_id: str, environment: ConnectionEnvironment, use_app_credentials: bool, credentials: CREDENTIALS, payload: dict = None) -> None:
287
285
  client = get_connections_client()
288
286
  try:
289
287
  existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
290
- if use_app_credentials:
291
- payload = {
292
- "app_credentials": credentials.model_dump(exclude_none=True)
293
- }
294
- else:
295
- payload = {
296
- "runtime_credentials": credentials.model_dump(exclude_none=True)
297
- }
288
+ if not payload:
289
+ if use_app_credentials:
290
+ payload = {
291
+ "app_credentials": credentials.model_dump(exclude_none=True)
292
+ }
293
+ else:
294
+ payload = {
295
+ "runtime_credentials": credentials.model_dump(exclude_none=True)
296
+ }
298
297
 
299
- logger.info(f"Setting credentials for environment '{environment}' on connection '{app_id}'")
300
298
  if existing_credentials:
301
299
  client.update_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials, payload=payload)
302
300
  else:
303
301
  client.create_credentials(app_id=app_id,env=environment, use_app_credentials=use_app_credentials, payload=payload)
304
- logger.info(f"Credentials successfully set for '{environment}' environment of connection '{app_id}'")
305
302
  except requests.HTTPError as e:
306
303
  response = e.response
307
304
  response_text = response.text
@@ -489,7 +486,20 @@ def set_credentials_connection(
489
486
  _validate_connection_params(type=conn_type, **kwargs)
490
487
  credentials = _get_credentials(type=conn_type, **kwargs)
491
488
 
492
- add_credentials(app_id=app_id, environment=environment, use_app_credentials=use_app_credentials, credentials=credentials)
489
+ # Special handling for oauth2 password flow as it sends both app_creds and runtime_creds
490
+ logger.info(f"Setting credentials for environment '{environment}' on connection '{app_id}'")
491
+ if conn_type == ConnectionType.OAUTH2_PASSWORD:
492
+ credentials_model = credentials.model_dump(exclude_none=True)
493
+ runtime_cred_keys = {"username", "password"}
494
+ app_creds = {"app_credentials": {k: credentials_model[k] for k in credentials_model if k not in runtime_cred_keys}}
495
+ runtime_creds = {"runtime_credentials": {k: credentials_model[k] for k in credentials_model if k in runtime_cred_keys}}
496
+
497
+ add_credentials(app_id=app_id, environment=environment, use_app_credentials=True, credentials=credentials, payload=app_creds)
498
+ add_credentials(app_id=app_id, environment=environment, use_app_credentials=False, credentials=credentials, payload=runtime_creds)
499
+ else:
500
+ add_credentials(app_id=app_id, environment=environment, use_app_credentials=use_app_credentials, credentials=credentials)
501
+
502
+ logger.info(f"Credentials successfully set for '{environment}' environment of connection '{app_id}'")
493
503
 
494
504
  def set_identity_provider_connection(
495
505
  app_id: str,
@@ -13,6 +13,7 @@ from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import
13
13
  from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
14
14
  from ibm_watsonx_orchestrate.client.connections import get_connections_client
15
15
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
16
+ from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload
16
17
 
17
18
  logger = logging.getLogger(__name__)
18
19
 
@@ -43,7 +44,8 @@ def parse_file(file: str) -> List[KnowledgeBase]:
43
44
  def to_column_name(col: str):
44
45
  return " ".join([word.capitalize() if not word[0].isupper() else word for word in col.split("_")])
45
46
 
46
- def get_file_name(path: str):
47
+ def get_file_name(file: str | FileUpload):
48
+ path = file.path if isinstance(file, FileUpload) else file
47
49
  # This name prettifying currently screws up file type detection on ingestion
48
50
  # return to_column_name(path.split("/")[-1].split(".")[0])
49
51
  return path.split("/")[-1]
@@ -55,7 +57,11 @@ def get_relative_file_path(path, dir):
55
57
  return f"{dir}{path.removeprefix('.')}"
56
58
  else:
57
59
  return f"{dir}/{path}"
58
-
60
+
61
+ def build_file_object(file_dir: str, file: str | FileUpload):
62
+ if isinstance(file, FileUpload):
63
+ return ('files', (get_file_name(file.path), open(get_relative_file_path(file.path, file_dir), 'rb')))
64
+ return ('files', (get_file_name(file), open(get_relative_file_path(file, file_dir), 'rb')))
59
65
 
60
66
  class KnowledgeBaseController:
61
67
  def __init__(self):
@@ -72,6 +78,21 @@ class KnowledgeBaseController:
72
78
 
73
79
  knowledge_bases = parse_file(file=file)
74
80
 
81
+ if app_id:
82
+ connections_client = get_connections_client()
83
+ connection_id = None
84
+
85
+ connections = connections_client.get_draft_by_app_id(app_id=app_id)
86
+ if not connections:
87
+ logger.error(f"No connection exists with the app-id '{app_id}'")
88
+ exit(1)
89
+
90
+ connection_id = connections.connection_id
91
+
92
+ for kb in knowledge_bases:
93
+ if kb.conversational_search_tool and kb.conversational_search_tool.index_config and len(kb.conversational_search_tool.index_config) > 0:
94
+ kb.conversational_search_tool.index_config[0].connection_id = connection_id
95
+
75
96
  existing_knowledge_bases = client.get_by_names([kb.name for kb in knowledge_bases])
76
97
 
77
98
  for kb in knowledge_bases:
@@ -86,32 +107,32 @@ class KnowledgeBaseController:
86
107
 
87
108
  kb.validate_documents_or_index_exists()
88
109
  if kb.documents:
89
- files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in kb.documents]
110
+ files = [build_file_object(file_dir, file) for file in kb.documents]
111
+ file_urls = { get_file_name(file): file.url for file in kb.documents if isinstance(file, FileUpload) and file.url }
90
112
 
91
113
  kb.prioritize_built_in_index = True
92
114
  payload = kb.model_dump(exclude_none=True);
93
115
  payload.pop('documents');
94
116
 
95
- client.create_built_in(payload=payload, files=files)
117
+ data = {
118
+ 'knowledge_base': json.dumps(payload),
119
+ 'file_urls': json.dumps(file_urls)
120
+ }
121
+
122
+ client.create_built_in(payload=data, files=files)
96
123
  else:
97
124
  if len(kb.conversational_search_tool.index_config) != 1:
98
125
  raise ValueError(f"Must provide exactly one conversational_search_tool.index_config. Provided {len(kb.conversational_search_tool.index_config)}.")
99
126
 
100
-
101
- if app_id:
102
- connections_client = get_connections_client()
103
- connection_id = None
104
- if app_id is not None:
105
- connections = connections_client.get_draft_by_app_id(app_id=app_id)
106
- if not connections:
107
- logger.error(f"No connection exists with the app-id '{app_id}'")
108
- exit(1)
109
-
110
- connection_id = connections.connection_id
111
- kb.conversational_search_tool.index_config[0].connection_id = connection_id
127
+ if (kb.conversational_search_tool.index_config[0].milvus or \
128
+ kb.conversational_search_tool.index_config[0].elastic_search) and \
129
+ not kb.conversational_search_tool.index_config[0].connection_id:
130
+ raise ValueError(f"Must provide credentials (via --app-id) when using milvus or elastic_search.")
112
131
 
113
132
  kb.prioritize_built_in_index = False
114
- client.create(payload=kb.model_dump(exclude_none=True))
133
+ data = { 'knowledge_base': json.dumps(kb.model_dump(exclude_none=True)) }
134
+
135
+ client.create(payload=data)
115
136
 
116
137
  logger.info(f"Successfully imported knowledge base '{kb.name}'")
117
138
  except ClientAPIException as e:
@@ -144,8 +165,8 @@ class KnowledgeBaseController:
144
165
  existing_docs = [doc.get("metadata", {}).get("original_file_name", "") for doc in status.get("documents", [])]
145
166
 
146
167
  removed_docs = existing_docs[:]
147
- for filepath in kb.documents:
148
- filename = get_file_name(filepath)
168
+ for file in kb.documents:
169
+ filename = get_file_name(file)
149
170
 
150
171
  if filename in existing_docs:
151
172
  logger.warning(f'Document \"{filename}\" already exists in knowledge base. Updating...')
@@ -155,17 +176,25 @@ class KnowledgeBaseController:
155
176
  logger.warning(f'Document \"{filename}\" removed from knowledge base.')
156
177
 
157
178
 
158
- files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in kb.documents]
179
+ files = [build_file_object(file_dir, file) for file in kb.documents]
180
+ file_urls = { get_file_name(file): file.url for file in kb.documents if isinstance(file, FileUpload) and file.url }
159
181
 
160
182
  kb.prioritize_built_in_index = True
161
183
  payload = kb.model_dump(exclude_none=True);
162
184
  payload.pop('documents');
163
185
 
164
- self.get_client().update_with_documents(knowledge_base_id, payload=payload, files=files)
186
+ data = {
187
+ 'knowledge_base': json.dumps(payload),
188
+ 'file_urls': json.dumps(file_urls)
189
+ }
190
+
191
+ self.get_client().update_with_documents(knowledge_base_id, payload=data, files=files)
165
192
  else:
166
193
  if kb.conversational_search_tool and kb.conversational_search_tool.index_config:
167
194
  kb.prioritize_built_in_index = False
168
- self.get_client().update(knowledge_base_id, kb.model_dump(exclude_none=True))
195
+
196
+ data = { 'knowledge_base': json.dumps(kb.model_dump(exclude_none=True)) }
197
+ self.get_client().update(knowledge_base_id, payload=data)
169
198
 
170
199
  logger.info(f"Knowledge base '{kb.name}' updated successfully")
171
200