ibm-watsonx-orchestrate 1.6.0b0__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/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -1
- 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/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +25 -14
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -21
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +55 -57
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +137 -10
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -3
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +25 -12
- ibm_watsonx_orchestrate/client/agents/agent_client.py +74 -6
- ibm_watsonx_orchestrate/client/base_api_client.py +2 -1
- ibm_watsonx_orchestrate/client/connections/connections_client.py +18 -9
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/service_instance.py +3 -3
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +10 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +400 -66
- ibm_watsonx_orchestrate/docker/default.env +44 -12
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +15 -5
- ibm_watsonx_orchestrate/flow_builder/utils.py +78 -48
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/METADATA +1 -1
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/RECORD +37 -32
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -240,6 +240,27 @@ def apply_llm_api_key_defaults(env_dict: dict) -> None:
|
|
240
240
|
env_dict.setdefault("ASSISTANT_EMBEDDINGS_SPACE_ID", space_value)
|
241
241
|
env_dict.setdefault("ROUTING_LLM_SPACE_ID", space_value)
|
242
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
|
+
|
243
264
|
def write_merged_env_file(merged_env: dict) -> Path:
|
244
265
|
tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
|
245
266
|
with tmp:
|
@@ -292,7 +313,7 @@ def get_persisted_user_env() -> dict | None:
|
|
292
313
|
user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
|
293
314
|
return user_env
|
294
315
|
|
295
|
-
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:
|
296
317
|
compose_path = get_compose_file()
|
297
318
|
compose_command = ensure_docker_compose_installed()
|
298
319
|
db_tag = read_env_file(final_env_file).get('DBTAG', None)
|
@@ -319,13 +340,17 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) ->
|
|
319
340
|
|
320
341
|
|
321
342
|
# Step 2: Start all remaining services (except DB)
|
343
|
+
profiles = []
|
322
344
|
if experimental_with_langfuse:
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
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]
|
329
354
|
|
330
355
|
command += [
|
331
356
|
"-f", str(compose_path),
|
@@ -444,6 +469,9 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
444
469
|
# do nothing, as the docker login here is not mandatory
|
445
470
|
pass
|
446
471
|
|
472
|
+
# Auto-configure callback IP for async tools
|
473
|
+
merged_env_dict = auto_configure_callback_ip(merged_env_dict)
|
474
|
+
|
447
475
|
#These are to removed warning and not used in UI component
|
448
476
|
if not 'WATSONX_SPACE_ID' in merged_env_dict:
|
449
477
|
merged_env_dict['WATSONX_SPACE_ID']='X'
|
@@ -621,8 +649,80 @@ def confirm_accepts_license_agreement(accepts_by_argument: bool):
|
|
621
649
|
logger.error('The terms and conditions were not accepted, exiting.')
|
622
650
|
exit(1)
|
623
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...")
|
624
667
|
|
668
|
+
system = platform.system()
|
669
|
+
ip = None
|
625
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
|
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}")
|
724
|
+
|
725
|
+
return merged_env_dict
|
626
726
|
|
627
727
|
@server_app.command(name="start")
|
628
728
|
def server_start(
|
@@ -636,6 +736,11 @@ def server_start(
|
|
636
736
|
'--with-langfuse', '-l',
|
637
737
|
help='Option to enable Langfuse support.'
|
638
738
|
),
|
739
|
+
experimental_with_ibm_telemetry: bool = typer.Option(
|
740
|
+
False,
|
741
|
+
'--with-ibm-telemetry', '-i',
|
742
|
+
help=''
|
743
|
+
),
|
639
744
|
persist_env_secrets: bool = typer.Option(
|
640
745
|
False,
|
641
746
|
'--persist-env-secrets', '-p',
|
@@ -647,6 +752,11 @@ def server_start(
|
|
647
752
|
"--accept-terms-and-conditions",
|
648
753
|
help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
|
649
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
|
+
),
|
650
760
|
):
|
651
761
|
confirm_accepts_license_agreement(accept_terms_and_conditions)
|
652
762
|
|
@@ -675,10 +785,21 @@ def server_start(
|
|
675
785
|
|
676
786
|
merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
|
677
787
|
|
678
|
-
#
|
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.
|
679
795
|
if experimental_with_langfuse:
|
680
796
|
merged_env_dict['LANGFUSE_ENABLED'] = 'true'
|
681
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'
|
682
803
|
|
683
804
|
try:
|
684
805
|
docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
|
@@ -690,7 +811,11 @@ def server_start(
|
|
690
811
|
|
691
812
|
|
692
813
|
final_env_file = write_merged_env_file(merged_env_dict)
|
693
|
-
|
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)
|
694
819
|
|
695
820
|
run_db_migration()
|
696
821
|
|
@@ -718,6 +843,8 @@ def server_start(
|
|
718
843
|
|
719
844
|
if experimental_with_langfuse:
|
720
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.")
|
721
848
|
|
722
849
|
@server_app.command(name="stop")
|
723
850
|
def server_stop(
|
@@ -825,4 +952,4 @@ def run_db_migration() -> None:
|
|
825
952
|
sys.exit(1)
|
826
953
|
|
827
954
|
if __name__ == "__main__":
|
828
|
-
server_app()
|
955
|
+
server_app()
|
@@ -243,9 +243,15 @@ class ToolkitController:
|
|
243
243
|
rich.print(JSON(json.dumps(tools_list, indent=4)))
|
244
244
|
else:
|
245
245
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
246
|
-
|
247
|
-
|
248
|
-
|
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])
|
249
255
|
|
250
256
|
tools_client = instantiate_client(ToolClient)
|
251
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
|
@@ -38,10 +39,9 @@ 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.flow_builder.utils import import_flow_model
|
45
45
|
|
46
46
|
from ibm_watsonx_orchestrate import __version__
|
47
47
|
|
@@ -345,17 +345,12 @@ 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
|
-
- The [bold green]
|
349
|
-
- 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].
|
350
349
|
|
351
350
|
"""
|
352
351
|
|
353
352
|
console.print(Panel(message, title="[bold blue]Flow tool support information[/bold blue]", border_style="bright_blue"))
|
354
353
|
|
355
|
-
|
356
|
-
if not is_local_dev():
|
357
|
-
raise typer.BadParameter(f"Flow tools are only supported in local environment.")
|
358
|
-
|
359
354
|
model = None
|
360
355
|
|
361
356
|
# Load the Flow JSON model from the file
|
@@ -422,7 +417,16 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
422
417
|
except Exception as e:
|
423
418
|
raise typer.BadParameter(f"Failed to load model from file {file}: {e}")
|
424
419
|
|
425
|
-
|
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
|
426
430
|
|
427
431
|
|
428
432
|
async def import_openapi_tool(file: str, connection_id: str) -> List[BaseTool]:
|
@@ -514,9 +518,16 @@ class ToolsController:
|
|
514
518
|
rich.print(JSON(json.dumps(tools_list, indent=4)))
|
515
519
|
else:
|
516
520
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
517
|
-
|
518
|
-
|
519
|
-
|
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])
|
520
531
|
|
521
532
|
connections_client = get_connections_client()
|
522
533
|
connections = connections_client.list()
|
@@ -558,6 +569,8 @@ class ToolsController:
|
|
558
569
|
tool_type=ToolKind.openapi
|
559
570
|
elif tool_binding.mcp is not None:
|
560
571
|
tool_type=ToolKind.mcp
|
572
|
+
elif tool_binding.flow is not None:
|
573
|
+
tool_type=ToolKind.flow
|
561
574
|
else:
|
562
575
|
tool_type="Unknown"
|
563
576
|
|
@@ -3,6 +3,74 @@ from typing_extensions import List, Optional
|
|
3
3
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
4
4
|
from pydantic import BaseModel
|
5
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
|
+
|
6
74
|
class AgentUpsertResponse(BaseModel):
|
7
75
|
id: Optional[str] = None
|
8
76
|
warning: Optional[str] = None
|
@@ -17,14 +85,14 @@ class AgentClient(BaseAPIClient):
|
|
17
85
|
|
18
86
|
|
19
87
|
def create(self, payload: dict) -> AgentUpsertResponse:
|
20
|
-
response = self._post(self.base_endpoint, data=payload)
|
88
|
+
response = self._post(self.base_endpoint, data=transform_agents_from_flat_agent_spec(payload))
|
21
89
|
return AgentUpsertResponse.model_validate(response)
|
22
90
|
|
23
91
|
def get(self) -> dict:
|
24
|
-
return self._get(f"{self.base_endpoint}?include_hidden=true")
|
92
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?include_hidden=true"))
|
25
93
|
|
26
94
|
def update(self, agent_id: str, data: dict) -> AgentUpsertResponse:
|
27
|
-
response = self._patch(f"{self.base_endpoint}/{agent_id}", data=data)
|
95
|
+
response = self._patch(f"{self.base_endpoint}/{agent_id}", data=transform_agents_from_flat_agent_spec(data))
|
28
96
|
return AgentUpsertResponse.model_validate(response)
|
29
97
|
|
30
98
|
def delete(self, agent_id: str) -> dict:
|
@@ -35,14 +103,14 @@ class AgentClient(BaseAPIClient):
|
|
35
103
|
|
36
104
|
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
37
105
|
formatted_agent_names = [f"names={x}" for x in agent_names]
|
38
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true")
|
106
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true"))
|
39
107
|
|
40
108
|
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
41
109
|
if agent_id is None:
|
42
110
|
return ""
|
43
111
|
else:
|
44
112
|
try:
|
45
|
-
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}"))
|
46
114
|
return agent
|
47
115
|
except ClientAPIException as e:
|
48
116
|
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
@@ -51,5 +119,5 @@ class AgentClient(BaseAPIClient):
|
|
51
119
|
|
52
120
|
def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
|
53
121
|
formatted_agent_ids = [f"ids={x}" for x in agent_ids]
|
54
|
-
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true")
|
122
|
+
return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true"))
|
55
123
|
|
@@ -36,6 +36,8 @@ class BaseAPIClient:
|
|
36
36
|
|
37
37
|
if not self.is_local:
|
38
38
|
self.base_url = f"{self.base_url}/v1/orchestrate"
|
39
|
+
else:
|
40
|
+
self.base_url = f"{self.base_url}/v1"
|
39
41
|
|
40
42
|
def _get_headers(self) -> dict:
|
41
43
|
headers = {}
|
@@ -46,7 +48,6 @@ class BaseAPIClient:
|
|
46
48
|
return headers
|
47
49
|
|
48
50
|
def _get(self, path: str, params: dict = None, data=None, return_raw=False) -> dict:
|
49
|
-
|
50
51
|
url = f"{self.base_url}{path}"
|
51
52
|
response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
|
52
53
|
self._check_response(response)
|
@@ -1,11 +1,12 @@
|
|
1
1
|
from typing import List
|
2
2
|
|
3
|
+
from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
|
3
4
|
from pydantic import BaseModel, ValidationError
|
4
5
|
from typing import Optional
|
5
6
|
|
6
7
|
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
7
8
|
from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
|
8
|
-
from ibm_watsonx_orchestrate.client.utils import is_cpd_env
|
9
|
+
from ibm_watsonx_orchestrate.client.utils import is_cpd_env, is_local_dev
|
9
10
|
|
10
11
|
import logging
|
11
12
|
logger = logging.getLogger(__name__)
|
@@ -42,6 +43,12 @@ class GetConnectionResponse(BaseModel):
|
|
42
43
|
|
43
44
|
|
44
45
|
class ConnectionsClient(BaseAPIClient):
|
46
|
+
def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, verify: str = None, authenticator: MCSPAuthenticator = None):
|
47
|
+
super(ConnectionsClient, self).__init__(base_url, api_key, is_local, verify, authenticator)
|
48
|
+
if is_local_dev(base_url):
|
49
|
+
self.base_url = f"{base_url.rstrip('/')}/api/v1/orchestrate"
|
50
|
+
else:
|
51
|
+
self.base_url = f"{base_url.rstrip('/')}/v1/orchestrate"
|
45
52
|
"""
|
46
53
|
Client to handle CRUD operations for Connections endpoint
|
47
54
|
"""
|
@@ -77,6 +84,8 @@ class ConnectionsClient(BaseAPIClient):
|
|
77
84
|
else f"/connections/applications?include_details=true"
|
78
85
|
)
|
79
86
|
res = self._get(path)
|
87
|
+
import json
|
88
|
+
json.dumps(res)
|
80
89
|
return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
|
81
90
|
except ValidationError as e:
|
82
91
|
logger.error("Recieved unexpected response from server")
|
@@ -107,25 +116,25 @@ class ConnectionsClient(BaseAPIClient):
|
|
107
116
|
|
108
117
|
# POST /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
109
118
|
# POST /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
110
|
-
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict,
|
111
|
-
if
|
119
|
+
def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
|
120
|
+
if use_app_credentials:
|
112
121
|
self._post(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
113
122
|
else:
|
114
123
|
self._post(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
115
124
|
|
116
125
|
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
117
126
|
# PATCH /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
118
|
-
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict,
|
119
|
-
if
|
127
|
+
def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
|
128
|
+
if use_app_credentials:
|
120
129
|
self._patch(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
|
121
130
|
else:
|
122
131
|
self._patch(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
|
123
132
|
|
124
133
|
# GET /api/v1/connections/applications/{app_id}/configs/credentials?env={env}
|
125
134
|
# GET /api/v1/connections/applications/{app_id}/configs/runtime_credentials?env={env}
|
126
|
-
def get_credentials(self, app_id: str, env: ConnectionEnvironment,
|
135
|
+
def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> dict:
|
127
136
|
try:
|
128
|
-
if
|
137
|
+
if use_app_credentials:
|
129
138
|
path = (
|
130
139
|
f"/connections/applications/{app_id}/credentials?env={env}"
|
131
140
|
if is_cpd_env(self.base_url)
|
@@ -146,8 +155,8 @@ class ConnectionsClient(BaseAPIClient):
|
|
146
155
|
|
147
156
|
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/credentials
|
148
157
|
# DELETE /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
|
149
|
-
def delete_credentials(self, app_id: str, env: ConnectionEnvironment,
|
150
|
-
if
|
158
|
+
def delete_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> None:
|
159
|
+
if use_app_credentials:
|
151
160
|
self._delete(f"/connections/applications/{app_id}/configs/{env}/credentials")
|
152
161
|
else:
|
153
162
|
self._delete(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials")
|
@@ -17,14 +17,16 @@ def _get_connections_manager_url() -> str:
|
|
17
17
|
url_parts = url.split(":")
|
18
18
|
url_parts[-1] = str(LOCAL_CONNECTION_MANAGER_PORT)
|
19
19
|
url = ":".join(url_parts)
|
20
|
-
url = url + "/api/v1/orchestrate"
|
21
20
|
return url
|
22
21
|
return None
|
23
22
|
|
24
23
|
def get_connections_client() -> ConnectionsClient:
|
25
24
|
return instantiate_client(client=ConnectionsClient, url=_get_connections_manager_url())
|
26
25
|
|
27
|
-
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType:
|
26
|
+
def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType | None:
|
27
|
+
if security_scheme is None and auth_type is None:
|
28
|
+
return None
|
29
|
+
|
28
30
|
if security_scheme != ConnectionSecurityScheme.OAUTH2:
|
29
31
|
return ConnectionType(security_scheme)
|
30
32
|
return ConnectionType(auth_type)
|
@@ -14,7 +14,7 @@ DEFAULT_TENANT = {
|
|
14
14
|
DEFAULT_USER = {"username": "wxo.archer@ibm.com", "password": "watsonx"}
|
15
15
|
DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
|
16
16
|
DEFAULT_LOCAL_AUTH_ENDPOINT = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/auth/token"
|
17
|
-
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/tenants"
|
17
|
+
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/tenants"
|
18
18
|
DEFAULT_LOCAL_TENANT_AUTH_ENDPOINT = "{}/api/v1/auth/token?tenant_id={}"
|
19
19
|
|
20
20
|
|
@@ -51,13 +51,13 @@ class ServiceInstance(BaseServiceInstance):
|
|
51
51
|
def _create_token(self) -> str:
|
52
52
|
if not self._credentials.auth_type:
|
53
53
|
if ".cloud.ibm.com" in self._credentials.url:
|
54
|
-
logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or '
|
54
|
+
logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
|
55
55
|
return self._authenticate(EnvironmentAuthType.IBM_CLOUD_IAM)
|
56
56
|
elif is_cpd_env(self._credentials.url):
|
57
|
-
logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or '
|
57
|
+
logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
|
58
58
|
return self._authenticate(EnvironmentAuthType.CPD)
|
59
59
|
else:
|
60
|
-
logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or '
|
60
|
+
logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd' ")
|
61
61
|
return self._authenticate(EnvironmentAuthType.MCSP)
|
62
62
|
else:
|
63
63
|
return self._authenticate(self._credentials.auth_type)
|
@@ -29,12 +29,17 @@ class TempusClient(BaseAPIClient):
|
|
29
29
|
authenticator=authenticator
|
30
30
|
)
|
31
31
|
|
32
|
+
def get_tempus_endpoint(self) -> str:
|
33
|
+
"""
|
34
|
+
Returns the Tempus endpoint URL
|
35
|
+
"""
|
36
|
+
return self.base_url
|
32
37
|
def create_update_flow_model(self, flow_id: str, model: dict) -> dict:
|
33
|
-
return self._post(f"/
|
38
|
+
return self._post(f"/flow-models/{flow_id}", data=model)
|
34
39
|
|
35
40
|
def run_flow(self, flow_id: str, input: dict) -> dict:
|
36
|
-
return self._post(f"/
|
41
|
+
return self._post(f"/flows/{flow_id}/versions/TIP/run", data=input)
|
37
42
|
|
38
43
|
def arun_flow(self, flow_id: str, input: dict) -> dict:
|
39
|
-
return self._post(f"/
|
44
|
+
return self._post(f"/flows/{flow_id}/versions/TIP/run/async", data=input)
|
40
45
|
|
@@ -47,6 +47,16 @@ def is_local_dev(url: str | None = None) -> bool:
|
|
47
47
|
|
48
48
|
return False
|
49
49
|
|
50
|
+
def is_ibm_cloud():
|
51
|
+
cfg = Config()
|
52
|
+
active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
53
|
+
url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
|
54
|
+
|
55
|
+
if url.__contains__("cloud.ibm.com"):
|
56
|
+
return True
|
57
|
+
return False
|
58
|
+
|
59
|
+
|
50
60
|
def is_cpd_env(url: str) -> bool:
|
51
61
|
if url.lower().startswith("https://cpd"):
|
52
62
|
return True
|