ibm-watsonx-orchestrate 1.9.0b1__py3-none-any.whl → 1.10.0b0__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/knowledge_bases/types.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/models/types.py +5 -0
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +19 -7
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +5 -3
- 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_controller.py +5 -2
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +103 -20
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +19 -12
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +17 -13
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +17 -4
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +6 -1
- 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 +14 -2
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +5 -3
- ibm_watsonx_orchestrate/client/voice_configurations/voice_configurations_client.py +75 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +23 -2
- ibm_watsonx_orchestrate/docker/default.env +16 -12
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +2 -2
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +29 -24
- ibm_watsonx_orchestrate/flow_builder/types.py +109 -17
- ibm_watsonx_orchestrate/flow_builder/utils.py +7 -3
- {ibm_watsonx_orchestrate-1.9.0b1.dist-info → ibm_watsonx_orchestrate-1.10.0b0.dist-info}/METADATA +1 -1
- {ibm_watsonx_orchestrate-1.9.0b1.dist-info → ibm_watsonx_orchestrate-1.10.0b0.dist-info}/RECORD +34 -29
- {ibm_watsonx_orchestrate-1.9.0b1.dist-info → ibm_watsonx_orchestrate-1.10.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.9.0b1.dist-info → ibm_watsonx_orchestrate-1.10.0b0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.9.0b1.dist-info → ibm_watsonx_orchestrate-1.10.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -10,10 +10,12 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
from requests import ConnectionError
|
11
11
|
from typing import List
|
12
12
|
from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
|
13
|
+
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import KnowledgeBaseSpec
|
13
14
|
from ibm_watsonx_orchestrate.agent_builder.tools import ToolSpec, ToolPermission, ToolRequestBody, ToolResponseBody
|
14
15
|
from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import AgentsController, AgentKind, SpecVersion
|
15
16
|
from ibm_watsonx_orchestrate.agent_builder.agents.types import DEFAULT_LLM, BaseAgentSpec
|
16
17
|
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
|
18
|
+
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
|
17
19
|
from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
|
18
20
|
from ibm_watsonx_orchestrate.client.copilot.cpe.copilot_cpe_client import CPEClient
|
19
21
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
@@ -56,10 +58,16 @@ def _get_incomplete_tool_from_name(tool_name: str) -> dict:
|
|
56
58
|
"input_schema": input_schema, "output_schema": output_schema})
|
57
59
|
return spec.model_dump()
|
58
60
|
|
61
|
+
|
59
62
|
def _get_incomplete_agent_from_name(agent_name: str) -> dict:
|
60
63
|
spec = BaseAgentSpec(**{"name": agent_name, "description": agent_name, "kind": AgentKind.NATIVE})
|
61
64
|
return spec.model_dump()
|
62
65
|
|
66
|
+
def _get_incomplete_knowledge_base_from_name(kb_name: str) -> dict:
|
67
|
+
spec = KnowledgeBaseSpec(**{"name": kb_name, "description": kb_name})
|
68
|
+
return spec.model_dump()
|
69
|
+
|
70
|
+
|
63
71
|
def _get_tools_from_names(tool_names: List[str]) -> List[dict]:
|
64
72
|
if not len(tool_names):
|
65
73
|
return []
|
@@ -115,6 +123,34 @@ def _get_agents_from_names(collaborators_names: List[str]) -> List[dict]:
|
|
115
123
|
|
116
124
|
return agents
|
117
125
|
|
126
|
+
def _get_knowledge_bases_from_names(kb_names: List[str]) -> List[dict]:
|
127
|
+
if not len(kb_names):
|
128
|
+
return []
|
129
|
+
|
130
|
+
kb_client = get_knowledge_bases_client()
|
131
|
+
|
132
|
+
try:
|
133
|
+
with _get_progress_spinner() as progress:
|
134
|
+
task = progress.add_task(description="Fetching Knowledge Bases", total=None)
|
135
|
+
knowledge_bases = kb_client.get_by_names(kb_names)
|
136
|
+
found_kbs = {kb.get("name") for kb in knowledge_bases}
|
137
|
+
progress.remove_task(task)
|
138
|
+
progress.refresh()
|
139
|
+
for kb_name in kb_names:
|
140
|
+
if kb_name not in found_kbs:
|
141
|
+
logger.warning(
|
142
|
+
f"Failed to find knowledge base named '{kb_name}'. Falling back to incomplete knowledge base definition. Copilot performance maybe effected.")
|
143
|
+
knowledge_bases.append(_get_incomplete_knowledge_base_from_name(kb_name))
|
144
|
+
except ConnectionError:
|
145
|
+
logger.warning(
|
146
|
+
f"Failed to fetch knowledge bases from server. For optimal results please start the server and import the relevant knowledge bases {', '.join(kb_names)}.")
|
147
|
+
knowledge_bases = []
|
148
|
+
for kb_name in kb_names:
|
149
|
+
knowledge_bases.append(_get_incomplete_knowledge_base_from_name(kb_name))
|
150
|
+
|
151
|
+
return knowledge_bases
|
152
|
+
|
153
|
+
|
118
154
|
def get_cpe_client() -> CPEClient:
|
119
155
|
url = os.getenv('CPE_URL', "http://localhost:8081")
|
120
156
|
return instantiate_client(client=CPEClient, url=url)
|
@@ -124,6 +160,10 @@ def get_tool_client(*args, **kwargs):
|
|
124
160
|
return instantiate_client(ToolClient)
|
125
161
|
|
126
162
|
|
163
|
+
def get_knowledge_bases_client(*args, **kwargs):
|
164
|
+
return instantiate_client(KnowledgeBaseClient)
|
165
|
+
|
166
|
+
|
127
167
|
def get_native_client(*args, **kwargs):
|
128
168
|
return instantiate_client(AgentClient)
|
129
169
|
|
@@ -144,18 +184,34 @@ def gather_utterances(max: int) -> list[str]:
|
|
144
184
|
return utterances
|
145
185
|
|
146
186
|
|
147
|
-
def
|
187
|
+
def get_knowledge_bases(client):
|
188
|
+
with _get_progress_spinner() as progress:
|
189
|
+
task = progress.add_task(description="Fetching Knowledge Bases", total=None)
|
190
|
+
try:
|
191
|
+
knowledge_bases = client.get()
|
192
|
+
progress.remove_task(task)
|
193
|
+
except ConnectionError:
|
194
|
+
knowledge_bases = []
|
195
|
+
progress.remove_task(task)
|
196
|
+
progress.refresh()
|
197
|
+
logger.warning("Failed to contact wxo server to fetch knowledge_bases. Proceeding with empty agent list")
|
198
|
+
return knowledge_bases
|
199
|
+
|
200
|
+
|
201
|
+
def get_deployed_tools_agents_and_knowledge_bases():
|
148
202
|
all_tools = find_tools_by_description(tool_client=get_tool_client(), description=None)
|
149
203
|
# TODO: this brings only the "native" agents. Can external and assistant agents also be collaborators?
|
150
204
|
all_agents = find_agents(agent_client=get_native_client())
|
151
|
-
|
205
|
+
all_knowledge_bases = get_knowledge_bases(get_knowledge_bases_client())
|
206
|
+
|
207
|
+
return {"tools": all_tools, "collaborators": all_agents, "knowledge_bases": all_knowledge_bases}
|
152
208
|
|
153
209
|
|
154
210
|
def pre_cpe_step(cpe_client):
|
155
|
-
|
211
|
+
tools_agents_and_knowledge_bases = get_deployed_tools_agents_and_knowledge_bases()
|
156
212
|
user_message = ""
|
157
213
|
with _get_progress_spinner() as progress:
|
158
|
-
task = progress.add_task(description="
|
214
|
+
task = progress.add_task(description="Initializing Prompt Engine", total=None)
|
159
215
|
response = cpe_client.submit_pre_cpe_chat(user_message=user_message)
|
160
216
|
progress.remove_task(task)
|
161
217
|
|
@@ -165,15 +221,26 @@ def pre_cpe_step(cpe_client):
|
|
165
221
|
rich.print('\n🤖 Copilot: ' + response["message"])
|
166
222
|
user_message = Prompt.ask("\n👤 You").strip()
|
167
223
|
message_content = {"user_message": user_message}
|
168
|
-
elif "description" in response and response["description"]:
|
224
|
+
elif "description" in response and response["description"]: # after we have a description, we pass the all tools
|
169
225
|
res["description"] = response["description"]
|
170
|
-
message_content =
|
171
|
-
elif "
|
172
|
-
|
173
|
-
res["
|
174
|
-
|
175
|
-
|
176
|
-
|
226
|
+
message_content = {"tools": tools_agents_and_knowledge_bases['tools']}
|
227
|
+
elif "tools" in response and response[
|
228
|
+
'tools'] is not None: # after tools were selected, we pass all collaborators
|
229
|
+
res["tools"] = [t for t in tools_agents_and_knowledge_bases["tools"] if
|
230
|
+
t["name"] in response["tools"]]
|
231
|
+
message_content = {"collaborators": tools_agents_and_knowledge_bases['collaborators']}
|
232
|
+
elif "collaborators" in response and response[
|
233
|
+
'collaborators'] is not None: # after we have collaborators, we pass all knowledge bases
|
234
|
+
res["collaborators"] = [a for a in tools_agents_and_knowledge_bases["collaborators"] if
|
235
|
+
a["name"] in response["collaborators"]]
|
236
|
+
message_content = {"knowledge_bases": tools_agents_and_knowledge_bases['knowledge_bases']}
|
237
|
+
elif "knowledge_bases" in response and response['knowledge_bases'] is not None: # after we have knowledge bases, we pass selected=True to mark that all selection were done
|
238
|
+
res["knowledge_bases"] = [a for a in tools_agents_and_knowledge_bases["knowledge_bases"] if
|
239
|
+
a["name"] in response["knowledge_bases"]]
|
240
|
+
message_content = {"selected": True}
|
241
|
+
elif "agent_name" in response and response['agent_name'] is not None: # once we have a name and style, this phase has ended
|
242
|
+
res["agent_name"] = response["agent_name"]
|
243
|
+
res["agent_style"] = response["agent_style"]
|
177
244
|
return res
|
178
245
|
with _get_progress_spinner() as progress:
|
179
246
|
task = progress.add_task(description="Thinking...", total=None)
|
@@ -194,6 +261,7 @@ def find_tools_by_description(description, tool_client):
|
|
194
261
|
logger.warning("Failed to contact wxo server to fetch tools. Proceeding with empty tool list")
|
195
262
|
return tools
|
196
263
|
|
264
|
+
|
197
265
|
def find_agents(agent_client):
|
198
266
|
with _get_progress_spinner() as progress:
|
199
267
|
task = progress.add_task(description="Fetching Agents", total=None)
|
@@ -279,16 +347,25 @@ def prompt_tune(agent_spec: str, output_file: str | None, samples_file: str | No
|
|
279
347
|
tools = _get_tools_from_names(agent.tools)
|
280
348
|
|
281
349
|
collaborators = _get_agents_from_names(agent.collaborators)
|
350
|
+
|
351
|
+
knowledge_bases = _get_knowledge_bases_from_names(agent.knowledge_base)
|
282
352
|
try:
|
283
|
-
new_prompt = talk_to_cpe(cpe_client=client,
|
284
|
-
|
285
|
-
|
353
|
+
new_prompt = talk_to_cpe(cpe_client=client,
|
354
|
+
samples_file=samples_file,
|
355
|
+
context_data={
|
356
|
+
"initial_instruction": instr,
|
357
|
+
'tools': tools,
|
358
|
+
'description': agent.description,
|
359
|
+
"collaborators": collaborators,
|
360
|
+
"knowledge_bases": knowledge_bases
|
361
|
+
})
|
286
362
|
except ConnectionError:
|
287
363
|
logger.error(
|
288
364
|
"Failed to connect to Copilot server. Please ensure Copilot is running via `orchestrate copilot start`")
|
289
365
|
sys.exit(1)
|
290
366
|
except ClientAPIException:
|
291
|
-
logger.error(
|
367
|
+
logger.error(
|
368
|
+
"An unexpected server error has occur with in the Copilot server. Please check the logs via `orchestrate server logs`")
|
292
369
|
sys.exit(1)
|
293
370
|
|
294
371
|
if new_prompt:
|
@@ -316,17 +393,21 @@ def create_agent(output_file: str, llm: str, samples_file: str | None, dry_run_f
|
|
316
393
|
"Failed to connect to Copilot server. Please ensure Copilot is running via `orchestrate copilot start`")
|
317
394
|
sys.exit(1)
|
318
395
|
except ClientAPIException:
|
319
|
-
logger.error(
|
396
|
+
logger.error(
|
397
|
+
"An unexpected server error has occur with in the Copilot server. Please check the logs via `orchestrate server logs`")
|
320
398
|
sys.exit(1)
|
321
|
-
|
399
|
+
|
322
400
|
tools = res["tools"]
|
323
401
|
collaborators = res["collaborators"]
|
402
|
+
knowledge_bases = res["knowledge_bases"]
|
324
403
|
description = res["description"]
|
325
404
|
agent_name = res["agent_name"]
|
326
405
|
agent_style = res["agent_style"]
|
327
406
|
|
328
407
|
# 4. discuss the instructions
|
329
|
-
instructions = talk_to_cpe(cpe_client, samples_file,
|
408
|
+
instructions = talk_to_cpe(cpe_client, samples_file,
|
409
|
+
{'description': description, 'tools': tools, 'collaborators': collaborators,
|
410
|
+
'knowledge_bases': knowledge_bases})
|
330
411
|
|
331
412
|
# 6. create and save the agent
|
332
413
|
llm = llm if llm else DEFAULT_LLM
|
@@ -334,7 +415,9 @@ def create_agent(output_file: str, llm: str, samples_file: str | None, dry_run_f
|
|
334
415
|
'style': agent_style,
|
335
416
|
'tools': [t['name'] for t in tools],
|
336
417
|
'llm': llm,
|
337
|
-
'collaborators': [c['name'] for c in collaborators]
|
418
|
+
'collaborators': [c['name'] for c in collaborators],
|
419
|
+
'knowledge_base': [k['name'] for k in knowledge_bases]
|
420
|
+
# generate_agent_spec expects knowledge_base and not knowledge_bases
|
338
421
|
}
|
339
422
|
agent = AgentsController.generate_agent_spec(agent_name, AgentKind.NATIVE, description, **params)
|
340
423
|
agent.instructions = instructions
|
@@ -72,6 +72,21 @@ class KnowledgeBaseController:
|
|
72
72
|
|
73
73
|
knowledge_bases = parse_file(file=file)
|
74
74
|
|
75
|
+
if app_id:
|
76
|
+
connections_client = get_connections_client()
|
77
|
+
connection_id = None
|
78
|
+
|
79
|
+
connections = connections_client.get_draft_by_app_id(app_id=app_id)
|
80
|
+
if not connections:
|
81
|
+
logger.error(f"No connection exists with the app-id '{app_id}'")
|
82
|
+
exit(1)
|
83
|
+
|
84
|
+
connection_id = connections.connection_id
|
85
|
+
|
86
|
+
for kb in knowledge_bases:
|
87
|
+
if kb.conversational_search_tool and kb.conversational_search_tool.index_config and len(kb.conversational_search_tool.index_config) > 0:
|
88
|
+
kb.conversational_search_tool.index_config[0].connection_id = connection_id
|
89
|
+
|
75
90
|
existing_knowledge_bases = client.get_by_names([kb.name for kb in knowledge_bases])
|
76
91
|
|
77
92
|
for kb in knowledge_bases:
|
@@ -97,18 +112,10 @@ class KnowledgeBaseController:
|
|
97
112
|
if len(kb.conversational_search_tool.index_config) != 1:
|
98
113
|
raise ValueError(f"Must provide exactly one conversational_search_tool.index_config. Provided {len(kb.conversational_search_tool.index_config)}.")
|
99
114
|
|
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
|
115
|
+
if (kb.conversational_search_tool.index_config[0].milvus or \
|
116
|
+
kb.conversational_search_tool.index_config[0].elastic_search) and \
|
117
|
+
not kb.conversational_search_tool.index_config[0].connection_id:
|
118
|
+
raise ValueError(f"Must provide credentials (via --app-id) when using milvus or elastic_search.")
|
112
119
|
|
113
120
|
kb.prioritize_built_in_index = False
|
114
121
|
client.create(payload=kb.model_dump(exclude_none=True))
|
@@ -10,19 +10,23 @@ _BASIC_PROVIDER_CONFIG_KEYS = {'provider', 'api_key', 'custom_host', 'url_to_fet
|
|
10
10
|
|
11
11
|
PROVIDER_EXTRA_PROPERTIES_LUT = {
|
12
12
|
ModelProvider.ANTHROPIC: {'anthropic_beta', 'anthropic_version'},
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
13
|
+
ModelProvider.AZURE_AI: {
|
14
|
+
'azure_resource_name',
|
15
|
+
'azure_deployment_id',
|
16
|
+
'azure_api_version',
|
17
|
+
'ad_auth',
|
18
|
+
'azure_auth_mode',
|
19
|
+
'azure_managed_client_id',
|
20
|
+
'azure_entra_client_id',
|
21
|
+
'azure_entra_client_secret',
|
22
|
+
'azure_entra_tenant_id',
|
23
|
+
'azure_ad_token',
|
24
|
+
'azure_model_name',
|
25
|
+
'azure_inference_deployment_name',
|
26
|
+
'azure_inference_api_version',
|
27
|
+
'azure_inference_extra_params',
|
28
|
+
'azure_inference_foundry_url'
|
29
|
+
},
|
26
30
|
ModelProvider.AZURE_OPENAI: {
|
27
31
|
'azure_resource_name',
|
28
32
|
'azure_deployment_id',
|
@@ -370,9 +370,13 @@ def get_persisted_user_env() -> dict | None:
|
|
370
370
|
user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
|
371
371
|
return user_env
|
372
372
|
|
373
|
-
def run_compose_lite(
|
374
|
-
|
375
|
-
|
373
|
+
def run_compose_lite(
|
374
|
+
final_env_file: Path,
|
375
|
+
experimental_with_langfuse=False,
|
376
|
+
experimental_with_ibm_telemetry=False,
|
377
|
+
with_doc_processing=False,
|
378
|
+
with_voice=False
|
379
|
+
) -> None:
|
376
380
|
compose_path = get_compose_file()
|
377
381
|
|
378
382
|
compose_command = ensure_docker_compose_installed()
|
@@ -408,6 +412,8 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
|
|
408
412
|
profiles.append("ibm-telemetry")
|
409
413
|
if with_doc_processing:
|
410
414
|
profiles.append("docproc")
|
415
|
+
if with_voice:
|
416
|
+
profiles.append("voice")
|
411
417
|
|
412
418
|
command = compose_command[:]
|
413
419
|
for profile in profiles:
|
@@ -855,6 +861,11 @@ def server_start(
|
|
855
861
|
'--compose-file', '-f',
|
856
862
|
help='Provide the path to a custom docker-compose file to use instead of the default compose file'
|
857
863
|
),
|
864
|
+
with_voice: bool = typer.Option(
|
865
|
+
False,
|
866
|
+
'--with-voice', '-v',
|
867
|
+
help='Enable voice controller to interact with the chat via voice channels'
|
868
|
+
),
|
858
869
|
):
|
859
870
|
confirm_accepts_license_agreement(accept_terms_and_conditions)
|
860
871
|
|
@@ -896,6 +907,7 @@ def server_start(
|
|
896
907
|
if experimental_with_ibm_telemetry:
|
897
908
|
merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
|
898
909
|
|
910
|
+
|
899
911
|
try:
|
900
912
|
dev_edition_source = get_dev_edition_source(merged_env_dict)
|
901
913
|
docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
|
@@ -908,7 +920,8 @@ def server_start(
|
|
908
920
|
run_compose_lite(final_env_file=final_env_file,
|
909
921
|
experimental_with_langfuse=experimental_with_langfuse,
|
910
922
|
experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
|
911
|
-
with_doc_processing=with_doc_processing
|
923
|
+
with_doc_processing=with_doc_processing,
|
924
|
+
with_voice=with_voice)
|
912
925
|
|
913
926
|
run_db_migration()
|
914
927
|
|
@@ -464,6 +464,11 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
464
464
|
continue
|
465
465
|
|
466
466
|
model = obj().to_json()
|
467
|
+
# Ensure metadata exists and is correct
|
468
|
+
if "metadata" not in model or not isinstance(model["metadata"], dict):
|
469
|
+
model["metadata"] = {}
|
470
|
+
if "source_kind" not in model["metadata"]:
|
471
|
+
model["metadata"]["source_kind"] = "adk/python"
|
467
472
|
break
|
468
473
|
|
469
474
|
elif file_path.suffix.lower() == ".json":
|
@@ -590,7 +595,7 @@ class ToolsController:
|
|
590
595
|
for tool in tools:
|
591
596
|
tools_list.append(json.loads(tool.dumps_spec()))
|
592
597
|
|
593
|
-
rich.
|
598
|
+
rich.print_json(json.dumps(tools_list, indent=4))
|
594
599
|
else:
|
595
600
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
596
601
|
column_args = {
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import sys
|
2
|
+
from typing import Annotated
|
3
|
+
import typer
|
4
|
+
import logging
|
5
|
+
|
6
|
+
from ibm_watsonx_orchestrate.cli.commands.voice_configurations.voice_configurations_controller import VoiceConfigurationsController
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
voice_configurations_app = typer.Typer(no_args_is_help=True)
|
11
|
+
|
12
|
+
@voice_configurations_app.command(name="import", help="Import a voice configuration into the active environment from a file")
|
13
|
+
def import_voice_config(
|
14
|
+
file: Annotated[
|
15
|
+
str,
|
16
|
+
typer.Option(
|
17
|
+
"--file",
|
18
|
+
"-f",
|
19
|
+
help="YAML file with voice configuraton definition"
|
20
|
+
)
|
21
|
+
],
|
22
|
+
):
|
23
|
+
voice_config_controller = VoiceConfigurationsController()
|
24
|
+
imported_config = voice_config_controller.import_voice_config(file)
|
25
|
+
voice_config_controller.publish_or_update_voice_config(imported_config)
|
26
|
+
|
27
|
+
@voice_configurations_app.command(name="remove", help="Remove a voice configuration from the active environment")
|
28
|
+
def remove_voice_config(
|
29
|
+
voice_config_name: Annotated[
|
30
|
+
str,
|
31
|
+
typer.Option(
|
32
|
+
"--name",
|
33
|
+
"-n",
|
34
|
+
help="name of the voice configuration to remove"
|
35
|
+
)
|
36
|
+
] = None,
|
37
|
+
):
|
38
|
+
voice_config_controller = VoiceConfigurationsController()
|
39
|
+
if voice_config_name:
|
40
|
+
voice_config_controller.remove_voice_config_by_name(voice_config_name)
|
41
|
+
else:
|
42
|
+
raise TypeError("You must specify the name of a voice configuration")
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
@voice_configurations_app.command(name="list", help="List all voice configurations in the active environment")
|
47
|
+
def list_voice_configs(
|
48
|
+
verbose: Annotated[
|
49
|
+
bool,
|
50
|
+
typer.Option(
|
51
|
+
"--verbose",
|
52
|
+
"-v",
|
53
|
+
help="List full details of all voice configurations in json format"
|
54
|
+
)
|
55
|
+
] = False,
|
56
|
+
):
|
57
|
+
voice_config_controller = VoiceConfigurationsController()
|
58
|
+
voice_config_controller.list_voice_configs(verbose)
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import json
|
2
|
+
import sys
|
3
|
+
import rich
|
4
|
+
import yaml
|
5
|
+
import logging
|
6
|
+
from ibm_watsonx_orchestrate.agent_builder.voice_configurations import VoiceConfiguration
|
7
|
+
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
8
|
+
from ibm_watsonx_orchestrate.client.voice_configurations.voice_configurations_client import VoiceConfigurationsClient
|
9
|
+
from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
|
10
|
+
|
11
|
+
logger = logging.getLogger(__name__)
|
12
|
+
|
13
|
+
class VoiceConfigurationsController:
|
14
|
+
|
15
|
+
def __init__(self):
|
16
|
+
self.voice_configs_client = None
|
17
|
+
|
18
|
+
def get_voice_configurations_client(self):
|
19
|
+
if not self.voice_configs_client:
|
20
|
+
self.voice_configs_client = instantiate_client(VoiceConfigurationsClient)
|
21
|
+
return self.voice_configs_client
|
22
|
+
|
23
|
+
|
24
|
+
def import_voice_config(self, file: str) -> VoiceConfiguration:
|
25
|
+
|
26
|
+
if file.endswith('.yaml') or file.endswith('.yml'):
|
27
|
+
with open(file, 'r') as f:
|
28
|
+
content = yaml.load(f, Loader=yaml.SafeLoader)
|
29
|
+
|
30
|
+
elif file.endswith(".json"):
|
31
|
+
with open(file, 'r') as f:
|
32
|
+
content = json.load(f)
|
33
|
+
|
34
|
+
else:
|
35
|
+
raise BadRequest("file must end in .yaml, .yml or .json")
|
36
|
+
|
37
|
+
return VoiceConfiguration.model_validate(content)
|
38
|
+
|
39
|
+
|
40
|
+
def fetch_voice_configs(self) -> list[VoiceConfiguration]:
|
41
|
+
client = self.get_voice_configurations_client()
|
42
|
+
res = client.list()
|
43
|
+
|
44
|
+
voice_configs = []
|
45
|
+
|
46
|
+
for config in res:
|
47
|
+
try:
|
48
|
+
voice_configs.append(VoiceConfiguration.model_validate(config))
|
49
|
+
except:
|
50
|
+
name = config.get('name', None)
|
51
|
+
logger.error(f"Config '{name}' could not be parsed")
|
52
|
+
|
53
|
+
return voice_configs
|
54
|
+
|
55
|
+
def get_voice_config(self, voice_config_id: str) -> VoiceConfiguration | None:
|
56
|
+
client = self.get_voice_configurations_client()
|
57
|
+
return client.get(voice_config_id)
|
58
|
+
|
59
|
+
def get_voice_config_by_name(self, voice_config_name: str) -> VoiceConfiguration | None:
|
60
|
+
client = self.get_voice_configurations_client()
|
61
|
+
configs = client.get_by_name(voice_config_name)
|
62
|
+
if len(configs) == 0:
|
63
|
+
logger.error(f"No voice_configs with the name '{voice_config_name}' found. Failed to get config")
|
64
|
+
sys.exit(1)
|
65
|
+
|
66
|
+
if len(configs) > 1:
|
67
|
+
logger.error(f"Multiple voice_configs with the name '{voice_config_name}' found. Failed to get config")
|
68
|
+
sys.exit(1)
|
69
|
+
|
70
|
+
return configs[0]
|
71
|
+
|
72
|
+
def list_voice_configs(self, verbose: bool) -> None:
|
73
|
+
voice_configs = self.fetch_voice_configs()
|
74
|
+
|
75
|
+
if verbose:
|
76
|
+
json_configs = [json.loads(x.dumps_spec()) for x in voice_configs]
|
77
|
+
rich.print_json(json.dumps(json_configs, indent=4))
|
78
|
+
else:
|
79
|
+
config_table = rich.table.Table(
|
80
|
+
show_header=True,
|
81
|
+
header_style="bold white",
|
82
|
+
title="Voice Configurations",
|
83
|
+
show_lines=True
|
84
|
+
)
|
85
|
+
|
86
|
+
column_args={
|
87
|
+
"Name" : {"overflow": "fold"},
|
88
|
+
"ID" : {"overflow": "fold"},
|
89
|
+
"STT Provider" : {"overflow": "fold"},
|
90
|
+
"TTS Provider" : {"overflow": "fold"},
|
91
|
+
"Attached Agents" : {}
|
92
|
+
}
|
93
|
+
|
94
|
+
for column in column_args:
|
95
|
+
config_table.add_column(column, **column_args[column])
|
96
|
+
|
97
|
+
for config in voice_configs:
|
98
|
+
attached_agents = [x.display_name or x.name or x.id for x in config.attached_agents]
|
99
|
+
config_table.add_row(
|
100
|
+
config.name,
|
101
|
+
config.voice_configuration_id,
|
102
|
+
config.speech_to_text.provider,
|
103
|
+
config.text_to_speech.provider,
|
104
|
+
",".join(attached_agents)
|
105
|
+
)
|
106
|
+
|
107
|
+
rich.print(config_table)
|
108
|
+
|
109
|
+
|
110
|
+
def create_voice_config(self, voice_config: VoiceConfiguration) -> str | None:
|
111
|
+
client = self.get_voice_configurations_client()
|
112
|
+
res = client.create(voice_config)
|
113
|
+
config_id = res.get("id",None)
|
114
|
+
if config_id:
|
115
|
+
logger.info(f"Sucessfully created voice config '{voice_config['name']}'. id: '{config_id}'")
|
116
|
+
|
117
|
+
return config_id
|
118
|
+
|
119
|
+
|
120
|
+
def update_voice_config_by_id(self, voice_config_id: str, voice_config: VoiceConfiguration) -> str | None:
|
121
|
+
client = self.get_voice_configurations_client()
|
122
|
+
res = client.update(voice_config_id,voice_config)
|
123
|
+
config_id = res.get("id",None)
|
124
|
+
if config_id:
|
125
|
+
logger.info(f"Sucessfully updated voice config '{voice_config['name']}'. id: '{config_id}'")
|
126
|
+
|
127
|
+
return config_id
|
128
|
+
|
129
|
+
def update_voice_config_by_name(self, voice_config_name: str, voice_config: VoiceConfiguration) -> str | None:
|
130
|
+
client = self.get_voice_configurations_client()
|
131
|
+
existing_config = client.get_by_name(voice_config_name)
|
132
|
+
|
133
|
+
if existing_config and len(existing_config) > 0:
|
134
|
+
config_id = existing_config[0].voice_configuration_id
|
135
|
+
client.update(config_id,voice_config)
|
136
|
+
else:
|
137
|
+
logger.warning(f"Voice config '{voice_config_name}' not found, creating new config instead")
|
138
|
+
config_id = self.create_voice_config(voice_config)
|
139
|
+
|
140
|
+
return config_id
|
141
|
+
|
142
|
+
def publish_or_update_voice_config(self, voice_config: VoiceConfiguration) -> str | None:
|
143
|
+
client = self.get_voice_configurations_client()
|
144
|
+
voice_config_name = voice_config.name
|
145
|
+
existing_config = client.get_by_name(voice_config_name)
|
146
|
+
|
147
|
+
if existing_config and len(existing_config) > 0:
|
148
|
+
config_id = existing_config[0].voice_configuration_id
|
149
|
+
client.update(config_id,voice_config)
|
150
|
+
else:
|
151
|
+
client.create(voice_config)
|
152
|
+
|
153
|
+
def remove_voice_config_by_id(self, voice_config_id: str) -> None:
|
154
|
+
client = self.get_voice_configurations_client()
|
155
|
+
client.delete(voice_config_id)
|
156
|
+
logger.info(f"Sucessfully deleted voice config '{voice_config_id}'")
|
157
|
+
|
158
|
+
def remove_voice_config_by_name(self, voice_config_name: str) -> None:
|
159
|
+
client = self.get_voice_configurations_client()
|
160
|
+
voice_config = self.get_voice_config_by_name(voice_config_name)
|
161
|
+
if voice_config:
|
162
|
+
client.delete(voice_config.voice_configuration_id)
|
163
|
+
logger.info(f"Sucessfully deleted voice config '{voice_config_name}'")
|
164
|
+
else:
|
165
|
+
logger.info(f"Voice config '{voice_config_name}' not found")
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
|
@@ -16,6 +16,7 @@ from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_comman
|
|
16
16
|
from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
|
17
17
|
from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_command import evaluation_app
|
18
18
|
from ibm_watsonx_orchestrate.cli.commands.copilot.copilot_command import copilot_app
|
19
|
+
from ibm_watsonx_orchestrate.cli.commands.voice_configurations.voice_configurations_command import voice_configurations_app
|
19
20
|
from ibm_watsonx_orchestrate.cli.init_helper import init_callback
|
20
21
|
|
21
22
|
import urllib3
|
@@ -35,6 +36,7 @@ app.add_typer(tools_app, name="tools", help='Interact with the tools in your act
|
|
35
36
|
app.add_typer(toolkits_app, name="toolkits", help="Interact with the toolkits in your active env")
|
36
37
|
app.add_typer(knowledge_bases_app, name="knowledge-bases", help="Upload knowledge your agents can search through to your active env")
|
37
38
|
app.add_typer(connections_app, name="connections", help='Interact with the agents in your active env')
|
39
|
+
app.add_typer(voice_configurations_app, name="voice-configs", help="Configure voice providers to enable voice interaction with your agents")
|
38
40
|
app.add_typer(server_app, name="server", help='Manipulate your local Orchestrate Developer Edition server [requires entitlement]')
|
39
41
|
app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Developer Edition server [requires entitlement]')
|
40
42
|
app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
|