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.
- ibm_watsonx_orchestrate/__init__.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +16 -12
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +47 -3
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +18 -15
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +19 -7
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +98 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +20 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +170 -1
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +7 -7
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +36 -26
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +51 -22
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +110 -16
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +43 -10
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +52 -25
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +5 -0
- ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_command.py +58 -0
- ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +173 -0
- ibm_watsonx_orchestrate/cli/main.py +2 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +64 -1
- ibm_watsonx_orchestrate/client/connections/connections_client.py +4 -3
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +4 -4
- ibm_watsonx_orchestrate/client/voice_configurations/voice_configurations_client.py +75 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +54 -5
- ibm_watsonx_orchestrate/docker/default.env +21 -13
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +2 -0
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +115 -31
- ibm_watsonx_orchestrate/flow_builder/node.py +39 -15
- ibm_watsonx_orchestrate/flow_builder/types.py +114 -25
- ibm_watsonx_orchestrate/run/connections.py +2 -2
- {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/METADATA +1 -1
- {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/RECORD +39 -34
- {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.9.0b2.dist-info → ibm_watsonx_orchestrate-1.10.0b1.dist-info}/entry_points.txt +0 -0
- {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
|
-
|
22
|
+
OAuth2AuthCodeCredentials,
|
23
23
|
OAuth2ClientCredentials,
|
24
24
|
# OAuth2ImplicitCredentials,
|
25
|
-
|
25
|
+
OAuth2PasswordCredentials,
|
26
26
|
OAuthOnBehalfOfCredentials,
|
27
27
|
KeyValueConnectionCredentials,
|
28
28
|
CREDENTIALS,
|
29
29
|
IdentityProviderCredentials,
|
30
|
-
OAUTH_CONNECTION_TYPES
|
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
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
-
|
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(
|
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 = [(
|
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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
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
|
148
|
-
filename = get_file_name(
|
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 = [(
|
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
|
-
|
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
|
-
|
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
|
|