ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.7.0a0__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 -1
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +33 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
- ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +122 -17
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -58
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +224 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +158 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +162 -27
- ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +37 -22
- ibm_watsonx_orchestrate/cli/config.py +2 -0
- ibm_watsonx_orchestrate/cli/main.py +6 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
- ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/credentials.py +4 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
- ibm_watsonx_orchestrate/client/service_instance.py +42 -1
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +37 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +425 -81
- ibm_watsonx_orchestrate/docker/default.env +53 -15
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
- ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/METADATA +2 -1
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/RECORD +68 -61
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -67,23 +67,28 @@ def docker_login(api_key: str, registry_url: str, username:str = "iamapikey") ->
|
|
67
67
|
logger.info("Successfully logged in to Docker.")
|
68
68
|
|
69
69
|
def docker_login_by_dev_edition_source(env_dict: dict, source: str) -> None:
|
70
|
-
if
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
70
|
+
if env_dict.get('WO_DEVELOPER_EDITION_SKIP_LOGIN', None) == 'true':
|
71
|
+
logger.info('WO_DEVELOPER_EDITION_SKIP_LOGIN is set to true, skipping login.')
|
72
|
+
logger.warning('If the developer edition images are not already pulled this call will fail without first setting WO_DEVELOPER_EDITION_SKIP_LOGIN=false')
|
73
|
+
else:
|
74
|
+
if not env_dict.get("REGISTRY_URL"):
|
75
|
+
raise ValueError("REGISTRY_URL is not set.")
|
76
|
+
registry_url = env_dict["REGISTRY_URL"].split("/")[0]
|
77
|
+
if source == "internal":
|
78
|
+
iam_api_key = env_dict.get("DOCKER_IAM_KEY")
|
79
|
+
if not iam_api_key:
|
80
|
+
raise ValueError("DOCKER_IAM_KEY is required in the environment file if WO_DEVELOPER_EDITION_SOURCE is set to 'internal'.")
|
81
|
+
docker_login(iam_api_key, registry_url, "iamapikey")
|
82
|
+
elif source == "myibm":
|
83
|
+
wo_entitlement_key = env_dict.get("WO_ENTITLEMENT_KEY")
|
84
|
+
if not wo_entitlement_key:
|
85
|
+
raise ValueError("WO_ENTITLEMENT_KEY is required in the environment file.")
|
86
|
+
docker_login(wo_entitlement_key, registry_url, "cp")
|
87
|
+
elif source == "orchestrate":
|
88
|
+
wo_auth_type = env_dict.get("WO_AUTH_TYPE")
|
89
|
+
api_key, username = get_docker_cred_by_wo_auth_type(env_dict, wo_auth_type)
|
90
|
+
docker_login(api_key, registry_url, username)
|
91
|
+
|
87
92
|
|
88
93
|
def get_compose_file() -> Path:
|
89
94
|
with resources.as_file(
|
@@ -165,6 +170,8 @@ def get_docker_cred_by_wo_auth_type(env_dict: dict, auth_type: str | None) -> tu
|
|
165
170
|
auth_type = "ibm_iam"
|
166
171
|
elif ".ibm.com" in instance_url:
|
167
172
|
auth_type = "mcsp"
|
173
|
+
elif "https://cpd" in instance_url:
|
174
|
+
auth_type = "cpd"
|
168
175
|
|
169
176
|
if auth_type in {"mcsp", "ibm_iam"}:
|
170
177
|
wo_api_key = env_dict.get("WO_API_KEY")
|
@@ -233,6 +240,27 @@ def apply_llm_api_key_defaults(env_dict: dict) -> None:
|
|
233
240
|
env_dict.setdefault("ASSISTANT_EMBEDDINGS_SPACE_ID", space_value)
|
234
241
|
env_dict.setdefault("ROUTING_LLM_SPACE_ID", space_value)
|
235
242
|
|
243
|
+
def _is_docker_container_running(container_name):
|
244
|
+
ensure_docker_installed()
|
245
|
+
command = [ "docker",
|
246
|
+
"ps",
|
247
|
+
"-f",
|
248
|
+
f"name={container_name}"
|
249
|
+
]
|
250
|
+
result = subprocess.run(command, env=os.environ, capture_output=True)
|
251
|
+
if container_name in str(result.stdout):
|
252
|
+
return True
|
253
|
+
return False
|
254
|
+
|
255
|
+
def _check_exclusive_observibility(langfuse_enabled: bool, ibm_tele_enabled: bool):
|
256
|
+
if langfuse_enabled and ibm_tele_enabled:
|
257
|
+
return False
|
258
|
+
if langfuse_enabled and _is_docker_container_running("docker-frontend-server-1"):
|
259
|
+
return False
|
260
|
+
if ibm_tele_enabled and _is_docker_container_running("docker-langfuse-web-1"):
|
261
|
+
return False
|
262
|
+
return True
|
263
|
+
|
236
264
|
def write_merged_env_file(merged_env: dict) -> Path:
|
237
265
|
tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
|
238
266
|
with tmp:
|
@@ -285,7 +313,7 @@ def get_persisted_user_env() -> dict | None:
|
|
285
313
|
user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
|
286
314
|
return user_env
|
287
315
|
|
288
|
-
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) -> None:
|
316
|
+
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_docproc=False) -> None:
|
289
317
|
compose_path = get_compose_file()
|
290
318
|
compose_command = ensure_docker_compose_installed()
|
291
319
|
db_tag = read_env_file(final_env_file).get('DBTAG', None)
|
@@ -312,13 +340,17 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) ->
|
|
312
340
|
|
313
341
|
|
314
342
|
# Step 2: Start all remaining services (except DB)
|
343
|
+
profiles = []
|
315
344
|
if experimental_with_langfuse:
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
345
|
+
profiles.append("langfuse")
|
346
|
+
if experimental_with_ibm_telemetry:
|
347
|
+
profiles.append("ibm-telemetry")
|
348
|
+
if with_docproc:
|
349
|
+
profiles.append("docproc")
|
350
|
+
|
351
|
+
command = compose_command[:]
|
352
|
+
for profile in profiles:
|
353
|
+
command += ["--profile", profile]
|
322
354
|
|
323
355
|
command += [
|
324
356
|
"-f", str(compose_path),
|
@@ -437,6 +469,9 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
437
469
|
# do nothing, as the docker login here is not mandatory
|
438
470
|
pass
|
439
471
|
|
472
|
+
# Auto-configure callback IP for async tools
|
473
|
+
merged_env_dict = auto_configure_callback_ip(merged_env_dict)
|
474
|
+
|
440
475
|
#These are to removed warning and not used in UI component
|
441
476
|
if not 'WATSONX_SPACE_ID' in merged_env_dict:
|
442
477
|
merged_env_dict['WATSONX_SPACE_ID']='X'
|
@@ -614,8 +649,80 @@ def confirm_accepts_license_agreement(accepts_by_argument: bool):
|
|
614
649
|
logger.error('The terms and conditions were not accepted, exiting.')
|
615
650
|
exit(1)
|
616
651
|
|
652
|
+
def auto_configure_callback_ip(merged_env_dict: dict) -> dict:
|
653
|
+
"""
|
654
|
+
Automatically detect and configure CALLBACK_HOST_URL if it's empty.
|
655
|
+
|
656
|
+
Args:
|
657
|
+
merged_env_dict: The merged environment dictionary
|
658
|
+
|
659
|
+
Returns:
|
660
|
+
Updated environment dictionary with CALLBACK_HOST_URL set
|
661
|
+
"""
|
662
|
+
callback_url = merged_env_dict.get('CALLBACK_HOST_URL', '').strip()
|
663
|
+
|
664
|
+
# Only auto-configure if CALLBACK_HOST_URL is empty
|
665
|
+
if not callback_url:
|
666
|
+
logger.info("Auto-detecting local IP address for async tool callbacks...")
|
667
|
+
|
668
|
+
system = platform.system()
|
669
|
+
ip = None
|
670
|
+
|
671
|
+
try:
|
672
|
+
if system in ("Linux", "Darwin"):
|
673
|
+
result = subprocess.run(["ifconfig"], capture_output=True, text=True, check=True)
|
674
|
+
lines = result.stdout.splitlines()
|
675
|
+
|
676
|
+
for line in lines:
|
677
|
+
line = line.strip()
|
678
|
+
# Unix ifconfig output format: "inet 192.168.1.100 netmask 0xffffff00 broadcast 192.168.1.255"
|
679
|
+
if line.startswith("inet ") and "127.0.0.1" not in line:
|
680
|
+
candidate_ip = line.split()[1]
|
681
|
+
# Validate IP is not loopback or link-local
|
682
|
+
if (candidate_ip and
|
683
|
+
not candidate_ip.startswith("127.") and
|
684
|
+
not candidate_ip.startswith("169.254")):
|
685
|
+
ip = candidate_ip
|
686
|
+
break
|
687
|
+
|
688
|
+
elif system == "Windows":
|
689
|
+
result = subprocess.run(["ipconfig"], capture_output=True, text=True, check=True)
|
690
|
+
lines = result.stdout.splitlines()
|
691
|
+
|
692
|
+
for line in lines:
|
693
|
+
line = line.strip()
|
694
|
+
# Windows ipconfig output format: " IPv4 Address. . . . . . . . . . . : 192.168.1.100"
|
695
|
+
if "IPv4 Address" in line and ":" in line:
|
696
|
+
candidate_ip = line.split(":")[-1].strip()
|
697
|
+
# Validate IP is not loopback or link-local
|
698
|
+
if (candidate_ip and
|
699
|
+
not candidate_ip.startswith("127.") and
|
700
|
+
not candidate_ip.startswith("169.254")):
|
701
|
+
ip = candidate_ip
|
702
|
+
break
|
617
703
|
|
704
|
+
else:
|
705
|
+
logger.warning(f"Unsupported platform: {system}")
|
706
|
+
ip = None
|
707
|
+
|
708
|
+
except Exception as e:
|
709
|
+
logger.debug(f"IP detection failed on {system}: {e}")
|
710
|
+
ip = None
|
711
|
+
|
712
|
+
if ip:
|
713
|
+
callback_url = f"http://{ip}:4321"
|
714
|
+
merged_env_dict['CALLBACK_HOST_URL'] = callback_url
|
715
|
+
logger.info(f"Auto-configured CALLBACK_HOST_URL to: {callback_url}")
|
716
|
+
else:
|
717
|
+
# Fallback for localhost
|
718
|
+
callback_url = "http://host.docker.internal:4321"
|
719
|
+
merged_env_dict['CALLBACK_HOST_URL'] = callback_url
|
720
|
+
logger.info(f"Using Docker internal URL: {callback_url}")
|
721
|
+
logger.info("For external tools, consider using ngrok or similar tunneling service.")
|
722
|
+
else:
|
723
|
+
logger.info(f"Using existing CALLBACK_HOST_URL: {callback_url}")
|
618
724
|
|
725
|
+
return merged_env_dict
|
619
726
|
|
620
727
|
@server_app.command(name="start")
|
621
728
|
def server_start(
|
@@ -629,6 +736,11 @@ def server_start(
|
|
629
736
|
'--with-langfuse', '-l',
|
630
737
|
help='Option to enable Langfuse support.'
|
631
738
|
),
|
739
|
+
experimental_with_ibm_telemetry: bool = typer.Option(
|
740
|
+
False,
|
741
|
+
'--with-ibm-telemetry', '-i',
|
742
|
+
help=''
|
743
|
+
),
|
632
744
|
persist_env_secrets: bool = typer.Option(
|
633
745
|
False,
|
634
746
|
'--persist-env-secrets', '-p',
|
@@ -640,6 +752,11 @@ def server_start(
|
|
640
752
|
"--accept-terms-and-conditions",
|
641
753
|
help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
|
642
754
|
),
|
755
|
+
with_docproc: bool = typer.Option(
|
756
|
+
False,
|
757
|
+
'--with-docproc', '-d',
|
758
|
+
help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
|
759
|
+
),
|
643
760
|
):
|
644
761
|
confirm_accepts_license_agreement(accept_terms_and_conditions)
|
645
762
|
|
@@ -668,10 +785,22 @@ def server_start(
|
|
668
785
|
|
669
786
|
merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
|
670
787
|
|
671
|
-
#
|
788
|
+
# Auto-configure callback IP for async tools
|
789
|
+
merged_env_dict = auto_configure_callback_ip(merged_env_dict)
|
790
|
+
if not _check_exclusive_observibility(experimental_with_langfuse, experimental_with_ibm_telemetry):
|
791
|
+
logger.error("Please select either langfuse or ibm telemetry for observability not both")
|
792
|
+
sys.exit(1)
|
793
|
+
|
794
|
+
# Add LANGFUSE_ENABLED and DOCPROC_ENABLED into the merged_env_dict, for tempus to pick up.
|
672
795
|
if experimental_with_langfuse:
|
673
796
|
merged_env_dict['LANGFUSE_ENABLED'] = 'true'
|
674
797
|
|
798
|
+
if with_docproc:
|
799
|
+
merged_env_dict['DOCPROC_ENABLED'] = 'true'
|
800
|
+
|
801
|
+
if experimental_with_ibm_telemetry:
|
802
|
+
merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
|
803
|
+
|
675
804
|
try:
|
676
805
|
docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
|
677
806
|
except ValueError as e:
|
@@ -682,7 +811,11 @@ def server_start(
|
|
682
811
|
|
683
812
|
|
684
813
|
final_env_file = write_merged_env_file(merged_env_dict)
|
685
|
-
|
814
|
+
|
815
|
+
run_compose_lite(final_env_file=final_env_file,
|
816
|
+
experimental_with_langfuse=experimental_with_langfuse,
|
817
|
+
experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
|
818
|
+
with_docproc=with_docproc)
|
686
819
|
|
687
820
|
run_db_migration()
|
688
821
|
|
@@ -710,6 +843,8 @@ def server_start(
|
|
710
843
|
|
711
844
|
if experimental_with_langfuse:
|
712
845
|
logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
|
846
|
+
if with_docproc:
|
847
|
+
logger.info(f"Document processing capabilities are now available for use in Flows (both ADK and runtime). Note: This option is currently available only in the Developer edition.")
|
713
848
|
|
714
849
|
@server_app.command(name="stop")
|
715
850
|
def server_stop(
|
@@ -817,4 +952,4 @@ def run_db_migration() -> None:
|
|
817
952
|
sys.exit(1)
|
818
953
|
|
819
954
|
if __name__ == "__main__":
|
820
|
-
server_app()
|
955
|
+
server_app()
|
@@ -26,7 +26,8 @@ def _infer_auth_type_from_instance_url(instance_url: str) -> WoAuthType:
|
|
26
26
|
return WoAuthType.IBM_IAM
|
27
27
|
if ".ibm.com" in instance_url:
|
28
28
|
return WoAuthType.MCSP
|
29
|
-
|
29
|
+
if "https://cpd" in instance_url:
|
30
|
+
return WoAuthType.CPD
|
30
31
|
|
31
32
|
|
32
33
|
class WatsonXAIEnvConfig(BaseModel):
|
@@ -213,9 +213,6 @@ class ToolkitController:
|
|
213
213
|
|
214
214
|
|
215
215
|
def remove_toolkit(self, name: str):
|
216
|
-
if not is_local_dev():
|
217
|
-
logger.error("This functionality is only available for Local Environments")
|
218
|
-
sys.exit(1)
|
219
216
|
try:
|
220
217
|
client = self.get_client()
|
221
218
|
draft_toolkits = client.get_draft_by_name(toolkit_name=name)
|
@@ -246,9 +243,15 @@ class ToolkitController:
|
|
246
243
|
rich.print(JSON(json.dumps(tools_list, indent=4)))
|
247
244
|
else:
|
248
245
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
249
|
-
|
250
|
-
|
251
|
-
|
246
|
+
column_args = {
|
247
|
+
"Name": {"overflow": "fold"},
|
248
|
+
"Kind": {},
|
249
|
+
"Description": {},
|
250
|
+
"Tools": {},
|
251
|
+
"App ID": {"overflow": "fold"}
|
252
|
+
}
|
253
|
+
for column in column_args:
|
254
|
+
table.add_column(column,**column_args[column])
|
252
255
|
|
253
256
|
tools_client = instantiate_client(ToolClient)
|
254
257
|
|
@@ -24,6 +24,7 @@ from rich.console import Console
|
|
24
24
|
from rich.panel import Panel
|
25
25
|
|
26
26
|
from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool, ToolSpec
|
27
|
+
from ibm_watsonx_orchestrate.agent_builder.tools.flow_tool import create_flow_json_tool
|
27
28
|
from ibm_watsonx_orchestrate.agent_builder.tools.openapi_tool import create_openapi_json_tools_from_uri,create_openapi_json_tools_from_content
|
28
29
|
from ibm_watsonx_orchestrate.cli.commands.models.models_controller import ModelHighlighter
|
29
30
|
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
@@ -33,15 +34,14 @@ from ibm_watsonx_orchestrate.cli.config import Config, CONTEXT_SECTION_HEADER, C
|
|
33
34
|
PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, \
|
34
35
|
DEFAULT_CONFIG_FILE_CONTENT
|
35
36
|
from ibm_watsonx_orchestrate.agent_builder.connections import ConnectionSecurityScheme, ExpectedCredentials
|
36
|
-
from ibm_watsonx_orchestrate.
|
37
|
+
from ibm_watsonx_orchestrate.flow_builder.flows.decorators import FlowWrapper
|
37
38
|
from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
|
38
39
|
from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
|
39
40
|
from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
|
40
41
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
|
42
|
+
from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_support_tools
|
41
43
|
from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
|
42
44
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
43
|
-
from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
|
44
|
-
from ibm_watsonx_orchestrate.experimental.flow_builder.utils import import_flow_model
|
45
45
|
|
46
46
|
from ibm_watsonx_orchestrate import __version__
|
47
47
|
|
@@ -339,24 +339,18 @@ async def import_flow_tool(file: str) -> None:
|
|
339
339
|
theme = rich.theme.Theme({"model.name": "bold cyan"})
|
340
340
|
console = rich.console.Console(highlighter=ModelHighlighter(), theme=theme)
|
341
341
|
|
342
|
-
message = f"""[bold cyan]Flow Tools
|
342
|
+
message = f"""[bold cyan]Flow Tools[/bold cyan]
|
343
343
|
|
344
344
|
The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
345
345
|
|
346
346
|
[bold cyan]Additional information:[/bold cyan]
|
347
347
|
|
348
|
-
-
|
349
|
-
- The [bold green]get_flow_status[/bold green] tool is being imported to support flow tools. Ensure [bold]both this tools and the one you are importing are added to your agent[/bold] to retrieve the flow output.
|
350
|
-
- Include additional instructions in your agent to call the [bold green]get_flow_status[/bold green] tool to retrieve the flow output. For example: [green]"If you get an instance_id, use the tool get_flow_status to retrieve the current status of a flow."[/green]
|
348
|
+
- The [bold green]Get flow status[/bold green] tool is being imported to support flow tools. This tool can query the status of a flow tool instance. You can add it to your agent using the UI or including the following tool name in your agent definition: [green]i__get_flow_status_intrinsic_tool__[/green].
|
351
349
|
|
352
350
|
"""
|
353
351
|
|
354
352
|
console.print(Panel(message, title="[bold blue]Flow tool support information[/bold blue]", border_style="bright_blue"))
|
355
353
|
|
356
|
-
|
357
|
-
if not is_local_dev():
|
358
|
-
raise typer.BadParameter(f"Flow tools are only supported in local environment.")
|
359
|
-
|
360
354
|
model = None
|
361
355
|
|
362
356
|
# Load the Flow JSON model from the file
|
@@ -423,7 +417,16 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
423
417
|
except Exception as e:
|
424
418
|
raise typer.BadParameter(f"Failed to load model from file {file}: {e}")
|
425
419
|
|
426
|
-
|
420
|
+
tool = create_flow_json_tool(name=model["spec"]["name"],
|
421
|
+
description=model["spec"]["description"],
|
422
|
+
permission="read_only",
|
423
|
+
flow_model=model)
|
424
|
+
|
425
|
+
tools = import_flow_support_tools()
|
426
|
+
|
427
|
+
tools.append(tool)
|
428
|
+
|
429
|
+
return tools
|
427
430
|
|
428
431
|
|
429
432
|
async def import_openapi_tool(file: str, connection_id: str) -> List[BaseTool]:
|
@@ -438,6 +441,10 @@ def _get_kind_from_spec(spec: dict) -> ToolKind:
|
|
438
441
|
return ToolKind.python
|
439
442
|
elif ToolKind.openapi in tool_binding:
|
440
443
|
return ToolKind.openapi
|
444
|
+
elif ToolKind.mcp in tool_binding:
|
445
|
+
return ToolKind.mcp
|
446
|
+
elif 'wxflows' in tool_binding:
|
447
|
+
return ToolKind.flow
|
441
448
|
else:
|
442
449
|
logger.error(f"Could not determine 'kind' of tool '{name}'")
|
443
450
|
sys.exit(1)
|
@@ -511,9 +518,16 @@ class ToolsController:
|
|
511
518
|
rich.print(JSON(json.dumps(tools_list, indent=4)))
|
512
519
|
else:
|
513
520
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
514
|
-
|
515
|
-
|
516
|
-
|
521
|
+
column_args = {
|
522
|
+
"Name": {"overflow": "fold"},
|
523
|
+
"Description": {},
|
524
|
+
"Permission": {},
|
525
|
+
"Type": {},
|
526
|
+
"Toolkit": {},
|
527
|
+
"App ID": {"overflow": "fold"}
|
528
|
+
}
|
529
|
+
for column in column_args:
|
530
|
+
table.add_column(column,**column_args[column])
|
517
531
|
|
518
532
|
connections_client = get_connections_client()
|
519
533
|
connections = connections_client.list()
|
@@ -555,19 +569,20 @@ class ToolsController:
|
|
555
569
|
tool_type=ToolKind.openapi
|
556
570
|
elif tool_binding.mcp is not None:
|
557
571
|
tool_type=ToolKind.mcp
|
572
|
+
elif tool_binding.flow is not None:
|
573
|
+
tool_type=ToolKind.flow
|
558
574
|
else:
|
559
575
|
tool_type="Unknown"
|
560
576
|
|
561
577
|
toolkit_name = ""
|
562
578
|
|
563
|
-
if
|
579
|
+
if tool.__tool_spec__.toolkit_id:
|
564
580
|
toolkit_client = instantiate_client(ToolKitClient)
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
toolkit_name = str(toolkit)
|
581
|
+
toolkit = toolkit_client.get_draft_by_id(tool.__tool_spec__.toolkit_id)
|
582
|
+
if isinstance(toolkit, dict) and "name" in toolkit:
|
583
|
+
toolkit_name = toolkit["name"]
|
584
|
+
elif toolkit:
|
585
|
+
toolkit_name = str(toolkit)
|
571
586
|
|
572
587
|
|
573
588
|
table.add_row(
|
@@ -26,6 +26,8 @@ ENV_WXO_URL_OPT = "wxo_url"
|
|
26
26
|
ENV_IAM_URL_OPT = "iam_url"
|
27
27
|
PROTECTED_ENV_NAME = "local"
|
28
28
|
ENV_AUTH_TYPE = "auth_type"
|
29
|
+
BYPASS_SSL = "bypass_ssl"
|
30
|
+
VERIFY = "verify"
|
29
31
|
ENV_ACCEPT_LICENSE = 'accepts_license_agreements'
|
30
32
|
|
31
33
|
DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
|
@@ -12,8 +12,13 @@ from ibm_watsonx_orchestrate.cli.commands.environment.environment_command import
|
|
12
12
|
from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import channel_app
|
13
13
|
from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
|
14
14
|
from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
|
15
|
+
from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_command import evaluation_app
|
15
16
|
from ibm_watsonx_orchestrate.cli.init_helper import init_callback
|
16
17
|
|
18
|
+
import urllib3
|
19
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
20
|
+
|
21
|
+
|
17
22
|
app = typer.Typer(
|
18
23
|
no_args_is_help=True,
|
19
24
|
pretty_exceptions_enable=False,
|
@@ -30,6 +35,7 @@ app.add_typer(server_app, name="server", help='Manipulate your local Orchestrate
|
|
30
35
|
app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Developer Edition server [requires entitlement]')
|
31
36
|
app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
|
32
37
|
app.add_typer(channel_app, name="channels", help="Configure channels where your agent can exist on (such as embedded webchat)")
|
38
|
+
app.add_typer(evaluation_app, name="evaluations", help='Evaluate the performance of your agents in your active env')
|
33
39
|
app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
|
34
40
|
|
35
41
|
if __name__ == "__main__":
|
@@ -1,7 +1,79 @@
|
|
1
1
|
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
2
|
-
from typing_extensions import List
|
2
|
+
from typing_extensions import List, Optional
|
3
3
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
4
|
+
from pydantic import BaseModel
|
4
5
|
|
6
|
+
def transform_agents_from_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
|
7
|
+
if isinstance(agents,list):
|
8
|
+
new_agents = []
|
9
|
+
for agent in agents:
|
10
|
+
new_agents.append(_transform_agent_from_flat_agent_spec(agent))
|
11
|
+
agents = new_agents
|
12
|
+
else:
|
13
|
+
agents = _transform_agent_from_flat_agent_spec(agents)
|
14
|
+
|
15
|
+
return agents
|
16
|
+
|
17
|
+
|
18
|
+
def _transform_agent_from_flat_agent_spec(agent_spec: dict ) -> dict:
|
19
|
+
transformed = {"additional_properties": {}}
|
20
|
+
for key,value in agent_spec.items():
|
21
|
+
if key == "starter_prompts":
|
22
|
+
if value:
|
23
|
+
value.pop("is_default_prompts",None)
|
24
|
+
value["customize"] = value.pop("prompts", [])
|
25
|
+
|
26
|
+
transformed["additional_properties"] |= { key: value }
|
27
|
+
|
28
|
+
elif key == "welcome_content":
|
29
|
+
if value:
|
30
|
+
value.pop("is_default_message", None)
|
31
|
+
|
32
|
+
transformed["additional_properties"] |= { key: value }
|
33
|
+
|
34
|
+
else:
|
35
|
+
transformed |= { key: value }
|
36
|
+
|
37
|
+
return transformed
|
38
|
+
|
39
|
+
def transform_agents_to_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
|
40
|
+
if isinstance(agents,list):
|
41
|
+
new_agents = []
|
42
|
+
for agent in agents:
|
43
|
+
new_agents.append(_transform_agent_to_flat_agent_spec(agent))
|
44
|
+
agents = new_agents
|
45
|
+
else:
|
46
|
+
agents = _transform_agent_to_flat_agent_spec(agents)
|
47
|
+
|
48
|
+
return agents
|
49
|
+
|
50
|
+
def _transform_agent_to_flat_agent_spec(agent_spec: dict ) -> dict:
|
51
|
+
additional_properties = agent_spec.get("additional_properties", None)
|
52
|
+
if not additional_properties:
|
53
|
+
return agent_spec
|
54
|
+
|
55
|
+
transformed = agent_spec
|
56
|
+
for key,value in additional_properties.items():
|
57
|
+
if key == "starter_prompts":
|
58
|
+
if value:
|
59
|
+
value["is_default_prompts"] = False
|
60
|
+
value["prompts"] = value.pop("customize", [])
|
61
|
+
|
62
|
+
transformed[key] = value
|
63
|
+
|
64
|
+
elif key == "welcome_content":
|
65
|
+
if value:
|
66
|
+
value["is_default_message"] = False
|
67
|
+
|
68
|
+
transformed[key] = value
|
69
|
+
|
70
|
+
transformed.pop("additional_properties",None)
|
71
|
+
|
72
|
+
return transformed
|
73
|
+
|
74
|
+
class AgentUpsertResponse(BaseModel):
|
75
|
+
id: Optional[str] = None
|
76
|
+
warning: Optional[str] = None
|
5
77
|
|
6
78
|
class AgentClient(BaseAPIClient):
|
7
79
|
"""
|
@@ -12,14 +84,16 @@ class AgentClient(BaseAPIClient):
|
|
12
84
|
self.base_endpoint = "/orchestrate/agents" if is_local_dev(self.base_url) else "/agents"
|
13
85
|
|
14
86
|
|
15
|
-
def create(self, payload: dict) ->
|
16
|
-
|
87
|
+
def create(self, payload: dict) -> AgentUpsertResponse:
|
88
|
+
response = self._post(self.base_endpoint, data=transform_agents_from_flat_agent_spec(payload))
|
89
|
+
return AgentUpsertResponse.model_validate(response)
|
17
90
|
|
18
91
|
def get(self) -> dict:
|
19
|
-
return self._get(self.base_endpoint)
|
92
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?include_hidden=true"))
|
20
93
|
|
21
|
-
def update(self, agent_id: str, data: dict) ->
|
22
|
-
|
94
|
+
def update(self, agent_id: str, data: dict) -> AgentUpsertResponse:
|
95
|
+
response = self._patch(f"{self.base_endpoint}/{agent_id}", data=transform_agents_from_flat_agent_spec(data))
|
96
|
+
return AgentUpsertResponse.model_validate(response)
|
23
97
|
|
24
98
|
def delete(self, agent_id: str) -> dict:
|
25
99
|
return self._delete(f"{self.base_endpoint}/{agent_id}")
|
@@ -29,14 +103,14 @@ class AgentClient(BaseAPIClient):
|
|
29
103
|
|
30
104
|
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
31
105
|
formatted_agent_names = [f"names={x}" for x in agent_names]
|
32
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}")
|
106
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true"))
|
33
107
|
|
34
108
|
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
35
109
|
if agent_id is None:
|
36
110
|
return ""
|
37
111
|
else:
|
38
112
|
try:
|
39
|
-
agent = self._get(f"{self.base_endpoint}/{agent_id}")
|
113
|
+
agent = transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}/{agent_id}"))
|
40
114
|
return agent
|
41
115
|
except ClientAPIException as e:
|
42
116
|
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
@@ -45,5 +119,5 @@ class AgentClient(BaseAPIClient):
|
|
45
119
|
|
46
120
|
def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
|
47
121
|
formatted_agent_ids = [f"ids={x}" for x in agent_ids]
|
48
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}")
|
122
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true"))
|
49
123
|
|
@@ -10,7 +10,7 @@ class AssistantAgentClient(BaseAPIClient):
|
|
10
10
|
return self._post("/assistants/watsonx", data=payload)
|
11
11
|
|
12
12
|
def get(self) -> dict:
|
13
|
-
return self._get("/assistants/watsonx")
|
13
|
+
return self._get("/assistants/watsonx?include_hidden=true")
|
14
14
|
|
15
15
|
def update(self, agent_id: str, data: dict) -> dict:
|
16
16
|
return self._patch(f"/assistants/watsonx/{agent_id}", data=data)
|
@@ -23,7 +23,7 @@ class AssistantAgentClient(BaseAPIClient):
|
|
23
23
|
|
24
24
|
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
25
25
|
formatted_agent_names = [f"names={x}" for x in agent_names]
|
26
|
-
return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_names)}")
|
26
|
+
return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_names)}&include_hidden=true")
|
27
27
|
|
28
28
|
def get_draft_by_id(self, agent_id: str) -> dict | str:
|
29
29
|
if agent_id is None:
|
@@ -39,4 +39,4 @@ class AssistantAgentClient(BaseAPIClient):
|
|
39
39
|
|
40
40
|
def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
|
41
41
|
formatted_agent_ids = [f"ids={x}" for x in agent_ids]
|
42
|
-
return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_ids)}")
|
42
|
+
return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_ids)}&include_hidden=true")
|
@@ -10,7 +10,7 @@ class ExternalAgentClient(BaseAPIClient):
|
|
10
10
|
return self._post("/agents/external-chat", data=payload)
|
11
11
|
|
12
12
|
def get(self) -> dict:
|
13
|
-
return self._get("/agents/external-chat")
|
13
|
+
return self._get("/agents/external-chat?include_hidden=true")
|
14
14
|
|
15
15
|
def update(self, agent_id: str, data: dict) -> dict:
|
16
16
|
return self._patch(f"/agents/external-chat/{agent_id}", data=data)
|
@@ -39,4 +39,4 @@ class ExternalAgentClient(BaseAPIClient):
|
|
39
39
|
|
40
40
|
def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
|
41
41
|
formatted_agent_ids = [f"ids={x}" for x in agent_ids]
|
42
|
-
return self._get(f"/agents/external-chat?{'&'.join(formatted_agent_ids)}")
|
42
|
+
return self._get(f"/agents/external-chat?{'&'.join(formatted_agent_ids)}&include_hidden=true")
|