ibm-watsonx-orchestrate 1.9.0.dev0__py3-none-any.whl → 1.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ibm_watsonx_orchestrate/__init__.py +1 -2
- 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 +45 -2
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +18 -15
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +1 -1
- 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 +53 -5
- ibm_watsonx_orchestrate/docker/default.env +22 -14
- 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.0.dev0.dist-info → ibm_watsonx_orchestrate-1.10.0.dist-info}/METADATA +1 -1
- {ibm_watsonx_orchestrate-1.9.0.dev0.dist-info → ibm_watsonx_orchestrate-1.10.0.dist-info}/RECORD +39 -34
- {ibm_watsonx_orchestrate-1.9.0.dev0.dist-info → ibm_watsonx_orchestrate-1.10.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.9.0.dev0.dist-info → ibm_watsonx_orchestrate-1.10.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.9.0.dev0.dist-info → ibm_watsonx_orchestrate-1.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -77,6 +77,8 @@ class BaseAgentSpec(BaseModel):
|
|
77
77
|
description: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
|
78
78
|
context_access_enabled: bool = True
|
79
79
|
context_variables: Optional[List[str]] = []
|
80
|
+
voice_configuration_id: Optional[str] = None
|
81
|
+
voice_configuration: Optional[str] = None
|
80
82
|
|
81
83
|
def dump_spec(self, file: str) -> None:
|
82
84
|
dumped = self.model_dump(mode='json', exclude_unset=True, exclude_none=True)
|
@@ -18,7 +18,7 @@ from .types import (
|
|
18
18
|
OAuth2AuthCodeCredentials,
|
19
19
|
OAuth2ClientCredentials,
|
20
20
|
# OAuth2ImplicitCredentials,
|
21
|
-
|
21
|
+
OAuth2PasswordCredentials,
|
22
22
|
OAuthOnBehalfOfCredentials,
|
23
23
|
KeyValueConnectionCredentials,
|
24
24
|
CONNECTION_KIND_SCHEME_MAPPING,
|
@@ -37,7 +37,7 @@ connection_type_security_schema_map = {
|
|
37
37
|
ConnectionType.OAUTH2_CLIENT_CREDS: ConnectionSecurityScheme.OAUTH2,
|
38
38
|
ConnectionType.OAUTH_ON_BEHALF_OF_FLOW: ConnectionSecurityScheme.OAUTH2,
|
39
39
|
# ConnectionType.OAUTH2_IMPLICIT: ConnectionSecurityScheme.OAUTH2,
|
40
|
-
|
40
|
+
ConnectionType.OAUTH2_PASSWORD: ConnectionSecurityScheme.OAUTH2
|
41
41
|
}
|
42
42
|
|
43
43
|
def _clean_env_vars(vars: dict[str:str], requirements: List[str], app_id: str) -> dict[str,str]:
|
@@ -8,7 +8,7 @@ class ConnectionKind(str, Enum):
|
|
8
8
|
api_key = 'api_key'
|
9
9
|
oauth_auth_code_flow = 'oauth_auth_code_flow'
|
10
10
|
# oauth_auth_implicit_flow = 'oauth_auth_implicit_flow'
|
11
|
-
|
11
|
+
oauth_auth_password_flow = 'oauth_auth_password_flow'
|
12
12
|
oauth_auth_client_credentials_flow = 'oauth_auth_client_credentials_flow'
|
13
13
|
oauth_auth_on_behalf_of_flow = 'oauth_auth_on_behalf_of_flow'
|
14
14
|
key_value = 'key_value'
|
@@ -34,7 +34,7 @@ class ConnectionPreference(str, Enum):
|
|
34
34
|
class ConnectionAuthType(str, Enum):
|
35
35
|
OAUTH2_AUTH_CODE = 'oauth2_auth_code'
|
36
36
|
# OAUTH2_IMPLICIT = 'oauth2_implicit'
|
37
|
-
|
37
|
+
OAUTH2_PASSWORD = 'oauth2_password'
|
38
38
|
OAUTH2_CLIENT_CREDS = 'oauth2_client_creds'
|
39
39
|
OAUTH_ON_BEHALF_OF_FLOW = 'oauth_on_behalf_of_flow'
|
40
40
|
|
@@ -65,7 +65,7 @@ class ConnectionType(str, Enum):
|
|
65
65
|
API_KEY_AUTH = ConnectionSecurityScheme.API_KEY_AUTH.value
|
66
66
|
OAUTH2_AUTH_CODE = ConnectionAuthType.OAUTH2_AUTH_CODE.value
|
67
67
|
# OAUTH2_IMPLICIT = ConnectionAuthType.OAUTH2_IMPLICIT.value
|
68
|
-
|
68
|
+
OAUTH2_PASSWORD = ConnectionAuthType.OAUTH2_PASSWORD.value
|
69
69
|
OAUTH2_CLIENT_CREDS = ConnectionAuthType.OAUTH2_CLIENT_CREDS.value
|
70
70
|
OAUTH_ON_BEHALF_OF_FLOW = ConnectionAuthType.OAUTH_ON_BEHALF_OF_FLOW.value
|
71
71
|
KEY_VALUE = ConnectionSecurityScheme.KEY_VALUE.value
|
@@ -90,7 +90,7 @@ OAUTH_CONNECTION_TYPES = {
|
|
90
90
|
ConnectionType.OAUTH2_AUTH_CODE,
|
91
91
|
ConnectionType.OAUTH2_CLIENT_CREDS,
|
92
92
|
# ConnectionType.OAUTH2_IMPLICIT,
|
93
|
-
|
93
|
+
ConnectionType.OAUTH2_PASSWORD,
|
94
94
|
ConnectionType.OAUTH_ON_BEHALF_OF_FLOW,
|
95
95
|
}
|
96
96
|
|
@@ -193,11 +193,15 @@ class OAuth2AuthCodeCredentials(BaseModel):
|
|
193
193
|
# client_id: str
|
194
194
|
# authorization_url: str
|
195
195
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
196
|
+
class OAuth2PasswordCredentials(BaseModel):
|
197
|
+
username: str
|
198
|
+
password: str
|
199
|
+
client_id: str
|
200
|
+
client_secret: str
|
201
|
+
token_url: str
|
202
|
+
scope: Optional[str] = None
|
203
|
+
grant_type: str = "password"
|
204
|
+
|
201
205
|
|
202
206
|
class OAuth2ClientCredentials(BaseModel):
|
203
207
|
client_id: str
|
@@ -226,7 +230,7 @@ CREDENTIALS_SET = Union[
|
|
226
230
|
APIKeyAuthCredentials,
|
227
231
|
OAuth2AuthCodeCredentials,
|
228
232
|
# OAuth2ImplicitCredentials,
|
229
|
-
|
233
|
+
OAuth2PasswordCredentials,
|
230
234
|
OAuth2ClientCredentials,
|
231
235
|
OAuthOnBehalfOfCredentials,
|
232
236
|
KeyValueConnectionCredentials
|
@@ -240,7 +244,7 @@ CONNECTION_KIND_SCHEME_MAPPING = {
|
|
240
244
|
ConnectionKind.api_key: ConnectionSecurityScheme.API_KEY_AUTH,
|
241
245
|
ConnectionKind.oauth_auth_code_flow: ConnectionSecurityScheme.OAUTH2,
|
242
246
|
# ConnectionKind.oauth_auth_implicit_flow: ConnectionSecurityScheme.OAUTH2,
|
243
|
-
|
247
|
+
ConnectionKind.oauth_auth_password_flow: ConnectionSecurityScheme.OAUTH2,
|
244
248
|
ConnectionKind.oauth_auth_client_credentials_flow: ConnectionSecurityScheme.OAUTH2,
|
245
249
|
ConnectionKind.oauth_auth_on_behalf_of_flow: ConnectionSecurityScheme.OAUTH2,
|
246
250
|
ConnectionKind.key_value: ConnectionSecurityScheme.KEY_VALUE,
|
@@ -250,7 +254,7 @@ CONNECTION_KIND_SCHEME_MAPPING = {
|
|
250
254
|
CONNECTION_KIND_OAUTH_TYPE_MAPPING = {
|
251
255
|
ConnectionKind.oauth_auth_code_flow: ConnectionAuthType.OAUTH2_AUTH_CODE,
|
252
256
|
# ConnectionKind.oauth_auth_implicit_flow: ConnectionAuthType.OAUTH2_IMPLICIT,
|
253
|
-
|
257
|
+
ConnectionKind.oauth_auth_password_flow: ConnectionAuthType.OAUTH2_PASSWORD,
|
254
258
|
ConnectionKind.oauth_auth_client_credentials_flow: ConnectionAuthType.OAUTH2_CLIENT_CREDS,
|
255
259
|
ConnectionKind.oauth_auth_on_behalf_of_flow: ConnectionAuthType.OAUTH_ON_BEHALF_OF_FLOW,
|
256
260
|
}
|
@@ -11,6 +11,7 @@ class SpecVersion(str, Enum):
|
|
11
11
|
class KnowledgeBaseKind(str, Enum):
|
12
12
|
KNOWLEDGE_BASE = "knowledge_base"
|
13
13
|
class RetrievalConfidenceThreshold(str, Enum):
|
14
|
+
Off = "Off"
|
14
15
|
Lowest = "Lowest"
|
15
16
|
Low = "Low"
|
16
17
|
High = "High"
|
@@ -24,6 +25,7 @@ class GeneratedResponseLength(str, Enum):
|
|
24
25
|
|
25
26
|
|
26
27
|
class ResponseConfidenceThreshold(str, Enum):
|
28
|
+
Off = "Off"
|
27
29
|
Lowest = "Lowest"
|
28
30
|
Low = "Low"
|
29
31
|
High = "High"
|
@@ -86,6 +88,8 @@ class GenerationConfiguration(BaseModel):
|
|
86
88
|
{
|
87
89
|
"model_id": "meta-llama/llama-3-1-70b-instruct",
|
88
90
|
"prompt_instruction": "When the documents are in different languages, you should respond in english.",
|
91
|
+
"max_docs_passed_to_llm": 10,
|
92
|
+
"retrieval_confidence_threshold": "Lowest",
|
89
93
|
"generated_response_length": "Moderate",
|
90
94
|
"display_text_no_results_found": "no docs found",
|
91
95
|
"display_text_connectivity_issue": "conn failed",
|
@@ -95,6 +99,7 @@ class GenerationConfiguration(BaseModel):
|
|
95
99
|
|
96
100
|
model_id: Optional[str] = None
|
97
101
|
prompt_instruction: Optional[str] = None
|
102
|
+
max_docs_passed_to_llm: Optional[int] = None
|
98
103
|
generated_response_length: Optional[GeneratedResponseLength] = None
|
99
104
|
display_text_no_results_found: Optional[str] = None
|
100
105
|
display_text_connectivity_issue: Optional[str] = None
|
@@ -198,12 +203,46 @@ class CustomSearchConnection(BaseModel):
|
|
198
203
|
filter: Optional[str] = None
|
199
204
|
metadata: Optional[dict] = None
|
200
205
|
|
206
|
+
class AstraDBConnection(BaseModel):
|
207
|
+
"""
|
208
|
+
example:
|
209
|
+
{
|
210
|
+
"api_endpoint": "https://xxx-us-east-2.apps.astra.datastax.com",
|
211
|
+
"key_space": "default_keyspace",
|
212
|
+
"collection": "search_wa_docs",
|
213
|
+
"embedding_model_id": "sentence-transformers/all-minilm-l12-v2",
|
214
|
+
"port": "443",
|
215
|
+
"filter": "productType: \"boots\"",
|
216
|
+
"limit": 5,
|
217
|
+
"field_mapping": {
|
218
|
+
"title": "title",
|
219
|
+
"body": "text",
|
220
|
+
"url": "some-url"
|
221
|
+
}
|
222
|
+
}
|
223
|
+
"""
|
224
|
+
api_endpoint: str
|
225
|
+
port: Optional[str] = None
|
226
|
+
server_cert: Optional[str] = None
|
227
|
+
keyspace: Optional[str]
|
228
|
+
data_type: str
|
229
|
+
collection: Optional[str]
|
230
|
+
table: Optional[str]
|
231
|
+
index_column: Optional[str]
|
232
|
+
embedding_mode: str
|
233
|
+
embedding_model_id: Optional[str]
|
234
|
+
credentials: dict
|
235
|
+
search_mode: str
|
236
|
+
limit: Optional[int] = 5
|
237
|
+
filter: Optional[str] = None
|
238
|
+
field_mapping: Optional[FieldMapping] = None
|
239
|
+
|
201
240
|
class IndexConnection(BaseModel):
|
202
241
|
connection_id: Optional[str] = None
|
203
242
|
milvus: Optional[MilvusConnection] = None
|
204
243
|
elastic_search: Optional[ElasticSearchConnection] = None
|
205
244
|
custom_search: Optional[CustomSearchConnection] = None
|
206
|
-
|
245
|
+
astradb: Optional[AstraDBConnection] = None
|
207
246
|
|
208
247
|
class ConversationalSearchConfig(BaseModel):
|
209
248
|
language: Optional[str] = None
|
@@ -219,6 +258,10 @@ class KnowledgeBaseBuiltInVectorIndexConfig(BaseModel):
|
|
219
258
|
chunk_size: Optional[int] = None
|
220
259
|
chunk_overlap: Optional[int] = None
|
221
260
|
limit: Optional[int] = None
|
261
|
+
|
262
|
+
class FileUpload(BaseModel):
|
263
|
+
path: str
|
264
|
+
url: Optional[str] = None
|
222
265
|
|
223
266
|
class KnowledgeBaseSpec(BaseModel):
|
224
267
|
"""Schema for a complete knowledge-base."""
|
@@ -237,4 +280,4 @@ class KnowledgeBaseSpec(BaseModel):
|
|
237
280
|
created_on: Optional[datetime] = None
|
238
281
|
updated_at: Optional[datetime] = None
|
239
282
|
# For import/update
|
240
|
-
documents: list[str] = None
|
283
|
+
documents: list[str] | list[FileUpload] = None
|
@@ -1,27 +1,36 @@
|
|
1
|
-
from typing import List, Dict, Optional
|
1
|
+
from typing import List, Dict, Optional, Union
|
2
2
|
from enum import Enum
|
3
|
-
from pydantic import BaseModel
|
3
|
+
from pydantic import BaseModel
|
4
4
|
|
5
5
|
class ToolkitKind(str, Enum):
|
6
6
|
MCP = "mcp"
|
7
7
|
|
8
|
-
class Language(str, Enum):
|
9
|
-
NODE = "node"
|
10
|
-
PYTHON ="python"
|
11
|
-
|
12
8
|
class ToolkitSource(str, Enum):
|
13
9
|
FILES = "files"
|
14
10
|
PUBLIC_REGISTRY = "public-registry"
|
15
11
|
|
12
|
+
class ToolkitTransportKind(str, Enum):
|
13
|
+
STREAMABLE_HTTP = "streamable_http"
|
14
|
+
SSE = "sse"
|
16
15
|
|
16
|
+
class Language(str, Enum):
|
17
|
+
NODE = "node"
|
18
|
+
PYTHON ="python"
|
17
19
|
|
18
|
-
class
|
19
|
-
source:
|
20
|
+
class LocalMcpModel(BaseModel):
|
21
|
+
source: ToolkitSource
|
20
22
|
command: str
|
21
23
|
args: List[str]
|
22
24
|
tools: List[str]
|
23
25
|
connections: Dict[str, str]
|
24
26
|
|
27
|
+
class RemoteMcpModel(BaseModel):
|
28
|
+
server_url: str
|
29
|
+
transport: ToolkitTransportKind
|
30
|
+
tools: List[str]
|
31
|
+
connections: Dict[str, str]
|
32
|
+
|
33
|
+
McpModel = Union[LocalMcpModel, RemoteMcpModel]
|
25
34
|
|
26
35
|
class ToolkitSpec(BaseModel):
|
27
36
|
id: str
|
@@ -33,10 +42,4 @@ class ToolkitSpec(BaseModel):
|
|
33
42
|
created_by: str
|
34
43
|
created_by_username: str
|
35
44
|
tools: List[str] | None
|
36
|
-
mcp: McpModel
|
37
|
-
|
38
|
-
@model_validator(mode='after')
|
39
|
-
def validate_tools_and_mcp(self) -> 'ToolkitSpec':
|
40
|
-
if self.mcp.source not in {"files", "public-registry"}:
|
41
|
-
raise ValueError("MCP source must be either 'files' or 'public-registry'.")
|
42
|
-
return self
|
45
|
+
mcp: McpModel
|
@@ -216,7 +216,7 @@ def _fix_optional(schema):
|
|
216
216
|
}
|
217
217
|
schema.properties[k] = JsonSchemaObject(**combined)
|
218
218
|
schema.properties[k].anyOf = None
|
219
|
-
|
219
|
+
|
220
220
|
for k in schema.properties.keys():
|
221
221
|
if schema.properties[k].type == 'object':
|
222
222
|
schema.properties[k] = _fix_optional(schema.properties[k])
|
@@ -0,0 +1 @@
|
|
1
|
+
from .types import VoiceConfiguration
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Annotated, Optional, List, Dict
|
3
|
+
from pydantic import BaseModel, Field, model_validator
|
4
|
+
|
5
|
+
def _validate_exactly_one_of_fields(object: BaseModel, object_name: str, fields: list[str]):
|
6
|
+
present_fields = [getattr(object,field) for field in fields if getattr(object,field) is not None]
|
7
|
+
|
8
|
+
if len(present_fields) != 1:
|
9
|
+
raise ValueError(f"{object_name} requires exactly one of {','.join(fields)}")
|
10
|
+
|
11
|
+
|
12
|
+
def _validate_language_uniqueness(config: BaseModel):
|
13
|
+
if hasattr(config,'language') and hasattr(config,'additional_languages'):
|
14
|
+
if config.language and config.additional_languages and config.language in config.additional_languages:
|
15
|
+
raise ValueError(f"Language '{config.language}' cannot be in both the default language and additional_languages")
|
16
|
+
|
17
|
+
|
18
|
+
class WatsonSTTConfig(BaseModel):
|
19
|
+
api_url: Annotated[str, Field(min_length=1,max_length=2048)]
|
20
|
+
api_key: Optional[Annotated[str, Field(min_length=1,max_length=2048)]] = None
|
21
|
+
bearer_token: Optional[Annotated[str, Field(min_length=1,max_length=2048)]] = None
|
22
|
+
model: Annotated[str, Field(min_length=1,max_length=256)]
|
23
|
+
|
24
|
+
class EmotechSTTConfig(BaseModel):
|
25
|
+
api_key: Annotated[str,Field(min_length=1,max_length=2048)]
|
26
|
+
api_url: Annotated[str,Field(min_length=1,max_length=2048)]
|
27
|
+
|
28
|
+
|
29
|
+
class SpeechToTextConfig(BaseModel):
|
30
|
+
provider: Annotated[str, Field(min_length=1,max_length=128)]
|
31
|
+
watson_stt_config: Optional[WatsonSTTConfig] = None
|
32
|
+
emotech_stt_config: Optional[EmotechSTTConfig] = None
|
33
|
+
|
34
|
+
@model_validator(mode='after')
|
35
|
+
def validate_providers(self):
|
36
|
+
_validate_exactly_one_of_fields(self,'SpeechToTextConfig',['watson_stt_config','emotech_stt_config'])
|
37
|
+
return self
|
38
|
+
|
39
|
+
class WatsonTTSConfig(BaseModel):
|
40
|
+
api_url: Annotated[str, Field(min_length=1,max_length=2048)]
|
41
|
+
api_key: Optional[Annotated[str, Field(min_length=1,max_length=2048)]] = None
|
42
|
+
bearer_token: Optional[Annotated[str, Field(min_length=1,max_length=2048)]] = None
|
43
|
+
voice: Annotated[str, Field(min_length=1,max_length=128)]
|
44
|
+
rate_percentage: Optional[int] = None
|
45
|
+
pitch_percentage: Optional[int] = None
|
46
|
+
language: Optional[str] = None
|
47
|
+
|
48
|
+
class EmotechTTSConfig(BaseModel):
|
49
|
+
api_url: Annotated[str, Field(min_length=1,max_length=2048)]
|
50
|
+
api_key: Annotated[str, Field(min_length=1,max_length=2048)]
|
51
|
+
voice: Optional[Annotated[str, Field(min_length=1,max_length=128)]]
|
52
|
+
|
53
|
+
class TextToSpeechConfig(BaseModel):
|
54
|
+
provider: Annotated[str, Field(min_length=1,max_length=128)]
|
55
|
+
watson_tts_config: Optional[WatsonTTSConfig] = None
|
56
|
+
emotech_tts_config: Optional[EmotechTTSConfig] = None
|
57
|
+
|
58
|
+
@model_validator(mode='after')
|
59
|
+
def validate_providers(self):
|
60
|
+
_validate_exactly_one_of_fields(self,'TextToSpeechConfig',['watson_tts_config','emotech_tts_config'])
|
61
|
+
return self
|
62
|
+
|
63
|
+
class AdditionalProperties(BaseModel):
|
64
|
+
speech_to_text: Optional[SpeechToTextConfig] = None
|
65
|
+
text_to_speech: Optional[TextToSpeechConfig] = None
|
66
|
+
|
67
|
+
class DTMFInput(BaseModel):
|
68
|
+
inter_digit_timeout_ms: Optional[int] = None
|
69
|
+
termination_key: Optional[str] = None
|
70
|
+
maximum_count: Optional[int] = None
|
71
|
+
ignore_speech: Optional[bool] = None
|
72
|
+
|
73
|
+
class AttachedAgent(BaseModel):
|
74
|
+
id: str
|
75
|
+
name: Optional[str] = None
|
76
|
+
display_name: Optional[str] = None
|
77
|
+
|
78
|
+
class VoiceConfiguration(BaseModel):
|
79
|
+
name: Annotated[str, Field(min_length=1,max_length=128)]
|
80
|
+
speech_to_text: SpeechToTextConfig
|
81
|
+
text_to_speech: TextToSpeechConfig
|
82
|
+
language: Optional[Annotated[str,Field(min_length=2,max_length=16)]] = None
|
83
|
+
additional_languages: Optional[dict[str,AdditionalProperties]] = None
|
84
|
+
dtmf_input: Optional[DTMFInput] = None
|
85
|
+
voice_configuration_id: Optional[str] = None
|
86
|
+
tenant_id: Optional[Annotated[str, Field(min_length=1,max_length=128)]] = None
|
87
|
+
attached_agents: Optional[list[AttachedAgent]] = None
|
88
|
+
|
89
|
+
@model_validator(mode='after')
|
90
|
+
def validate_language(self):
|
91
|
+
_validate_language_uniqueness(self)
|
92
|
+
return self
|
93
|
+
|
94
|
+
def dumps_spec(self) -> str:
|
95
|
+
dumped = self.model_dump(mode='json', exclude_none=True)
|
96
|
+
return json.dumps(dumped, indent=2)
|
97
|
+
|
98
|
+
|
@@ -253,3 +253,23 @@ def export_agent(
|
|
253
253
|
):
|
254
254
|
agents_controller = AgentsController()
|
255
255
|
agents_controller.export_agent(name=name, kind=kind, output_path=output_file, agent_only_flag=agent_only_flag)
|
256
|
+
|
257
|
+
@agents_app.command(name="deploy", help="Deploy Agent")
|
258
|
+
def deploy_agent(
|
259
|
+
name: Annotated[
|
260
|
+
str,
|
261
|
+
typer.Option("--name", "-n", help="Name of the agent you wish to deploy"),
|
262
|
+
]
|
263
|
+
):
|
264
|
+
agents_controller = AgentsController()
|
265
|
+
agents_controller.deploy_agent(name=name)
|
266
|
+
|
267
|
+
@agents_app.command(name="undeploy", help="Undeploy Agent")
|
268
|
+
def undeploy_agent(
|
269
|
+
name: Annotated[
|
270
|
+
str,
|
271
|
+
typer.Option("--name", "-n", help="Name of the agent you wish to undeploy"),
|
272
|
+
]
|
273
|
+
):
|
274
|
+
agents_controller = AgentsController()
|
275
|
+
agents_controller.undeploy_agent(name=name)
|
@@ -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
|
+
|