ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
- ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
- ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +38 -9
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +25 -10
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +19 -11
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +10 -9
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +35 -7
- ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +35 -25
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +12 -12
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +3 -1
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +134 -36
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +42 -11
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +36 -20
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +59 -10
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
- ibm_watsonx_orchestrate/cli/config.py +3 -3
- ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
- ibm_watsonx_orchestrate/cli/main.py +5 -0
- ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
- ibm_watsonx_orchestrate/client/connections/connections_client.py +5 -30
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
- ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
- ibm_watsonx_orchestrate/client/service_instance.py +33 -7
- ibm_watsonx_orchestrate/client/utils.py +49 -8
- ibm_watsonx_orchestrate/docker/compose-lite.yml +25 -6
- ibm_watsonx_orchestrate/docker/default.env +26 -15
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +9 -4
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
- ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +131 -20
- ibm_watsonx_orchestrate/flow_builder/node.py +18 -1
- ibm_watsonx_orchestrate/flow_builder/types.py +271 -15
- ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
- ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/METADATA +5 -5
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/RECORD +60 -56
- ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -33,6 +33,21 @@ logger = logging.getLogger(__name__)
|
|
33
33
|
server_app = typer.Typer(no_args_is_help=True)
|
34
34
|
|
35
35
|
|
36
|
+
_ALWAYS_UNSET: set[str] = {
|
37
|
+
"WO_API_KEY",
|
38
|
+
"WO_INSTANCE",
|
39
|
+
"DOCKER_IAM_KEY",
|
40
|
+
"WO_DEVELOPER_EDITION_SOURCE",
|
41
|
+
"WATSONX_SPACE_ID",
|
42
|
+
"WATSONX_APIKEY",
|
43
|
+
"WO_USERNAME",
|
44
|
+
"WO_PASSWORD",
|
45
|
+
}
|
46
|
+
|
47
|
+
def define_saas_wdu_runtime(value: str = "none") -> None:
|
48
|
+
cfg = Config()
|
49
|
+
cfg.write(USER_ENV_CACHE_HEADER,"SAAS_WDU_RUNTIME",value)
|
50
|
+
|
36
51
|
def ensure_docker_installed() -> None:
|
37
52
|
try:
|
38
53
|
subprocess.run(["docker", "--version"], check=True, capture_output=True)
|
@@ -261,6 +276,13 @@ def _check_exclusive_observibility(langfuse_enabled: bool, ibm_tele_enabled: boo
|
|
261
276
|
return False
|
262
277
|
return True
|
263
278
|
|
279
|
+
def _prepare_clean_env(env_file: Path) -> None:
|
280
|
+
"""Remove env vars so terminal definitions don't override"""
|
281
|
+
keys_from_file = set(dotenv_values(str(env_file)).keys())
|
282
|
+
keys_to_unset = keys_from_file | _ALWAYS_UNSET
|
283
|
+
for key in keys_to_unset:
|
284
|
+
os.environ.pop(key, None)
|
285
|
+
|
264
286
|
def write_merged_env_file(merged_env: dict) -> Path:
|
265
287
|
tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
|
266
288
|
with tmp:
|
@@ -293,7 +315,8 @@ NON_SECRET_ENV_ITEMS = {
|
|
293
315
|
"WO_INSTANCE",
|
294
316
|
"USE_SAAS_ML_TOOLS_RUNTIME",
|
295
317
|
"AUTHORIZATION_URL",
|
296
|
-
"OPENSOURCE_REGISTRY_PROXY"
|
318
|
+
"OPENSOURCE_REGISTRY_PROXY",
|
319
|
+
"SAAS_WDU_RUNTIME"
|
297
320
|
}
|
298
321
|
def persist_user_env(env: dict, include_secrets: bool = False) -> None:
|
299
322
|
if include_secrets:
|
@@ -313,9 +336,10 @@ def get_persisted_user_env() -> dict | None:
|
|
313
336
|
user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
|
314
337
|
return user_env
|
315
338
|
|
316
|
-
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False,
|
339
|
+
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_doc_processing=False) -> None:
|
317
340
|
compose_path = get_compose_file()
|
318
341
|
compose_command = ensure_docker_compose_installed()
|
342
|
+
_prepare_clean_env(final_env_file)
|
319
343
|
db_tag = read_env_file(final_env_file).get('DBTAG', None)
|
320
344
|
logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
|
321
345
|
|
@@ -345,7 +369,7 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
|
|
345
369
|
profiles.append("langfuse")
|
346
370
|
if experimental_with_ibm_telemetry:
|
347
371
|
profiles.append("ibm-telemetry")
|
348
|
-
if
|
372
|
+
if with_doc_processing:
|
349
373
|
profiles.append("docproc")
|
350
374
|
|
351
375
|
command = compose_command[:]
|
@@ -358,6 +382,8 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
|
|
358
382
|
"up",
|
359
383
|
"--scale",
|
360
384
|
"ui=0",
|
385
|
+
"--scale",
|
386
|
+
"cpe=0",
|
361
387
|
"-d",
|
362
388
|
"--remove-orphans",
|
363
389
|
]
|
@@ -428,6 +454,7 @@ def wait_for_wxo_ui_health_check(timeout_seconds=45, interval_seconds=2):
|
|
428
454
|
def run_compose_lite_ui(user_env_file: Path) -> bool:
|
429
455
|
compose_path = get_compose_file()
|
430
456
|
compose_command = ensure_docker_compose_installed()
|
457
|
+
_prepare_clean_env(user_env_file)
|
431
458
|
ensure_docker_installed()
|
432
459
|
|
433
460
|
default_env = read_env_file(get_default_env_file())
|
@@ -522,6 +549,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
522
549
|
def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> None:
|
523
550
|
compose_path = get_compose_file()
|
524
551
|
compose_command = ensure_docker_compose_installed()
|
552
|
+
_prepare_clean_env(user_env_file)
|
525
553
|
|
526
554
|
|
527
555
|
ensure_docker_installed()
|
@@ -565,6 +593,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
|
|
565
593
|
def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
|
566
594
|
compose_path = get_compose_file()
|
567
595
|
compose_command = ensure_docker_compose_installed()
|
596
|
+
_prepare_clean_env(final_env_file)
|
568
597
|
|
569
598
|
command = compose_command + [
|
570
599
|
'--profile', '*',
|
@@ -597,6 +626,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
|
|
597
626
|
def run_compose_lite_logs(final_env_file: Path, is_reset: bool = False) -> None:
|
598
627
|
compose_path = get_compose_file()
|
599
628
|
compose_command = ensure_docker_compose_installed()
|
629
|
+
_prepare_clean_env(final_env_file)
|
600
630
|
|
601
631
|
command = compose_command + [
|
602
632
|
"-f", str(compose_path),
|
@@ -752,14 +782,16 @@ def server_start(
|
|
752
782
|
"--accept-terms-and-conditions",
|
753
783
|
help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
|
754
784
|
),
|
755
|
-
|
785
|
+
with_doc_processing: bool = typer.Option(
|
756
786
|
False,
|
757
|
-
'--with-
|
787
|
+
'--with-doc-processing', '-d',
|
758
788
|
help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
|
759
789
|
),
|
760
790
|
):
|
761
791
|
confirm_accepts_license_agreement(accept_terms_and_conditions)
|
762
792
|
|
793
|
+
define_saas_wdu_runtime()
|
794
|
+
|
763
795
|
if user_env_file and not Path(user_env_file).exists():
|
764
796
|
logger.error(f"Error: The specified environment file '{user_env_file}' does not exist.")
|
765
797
|
sys.exit(1)
|
@@ -795,8 +827,9 @@ def server_start(
|
|
795
827
|
if experimental_with_langfuse:
|
796
828
|
merged_env_dict['LANGFUSE_ENABLED'] = 'true'
|
797
829
|
|
798
|
-
if
|
830
|
+
if with_doc_processing:
|
799
831
|
merged_env_dict['DOCPROC_ENABLED'] = 'true'
|
832
|
+
define_saas_wdu_runtime("local")
|
800
833
|
|
801
834
|
if experimental_with_ibm_telemetry:
|
802
835
|
merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
|
@@ -815,8 +848,8 @@ def server_start(
|
|
815
848
|
run_compose_lite(final_env_file=final_env_file,
|
816
849
|
experimental_with_langfuse=experimental_with_langfuse,
|
817
850
|
experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
|
818
|
-
|
819
|
-
|
851
|
+
with_doc_processing=with_doc_processing)
|
852
|
+
|
820
853
|
run_db_migration()
|
821
854
|
|
822
855
|
logger.info("Waiting for orchestrate server to be fully initialized and ready...")
|
@@ -843,8 +876,8 @@ def server_start(
|
|
843
876
|
|
844
877
|
if experimental_with_langfuse:
|
845
878
|
logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
|
846
|
-
if
|
847
|
-
logger.info(f"Document processing
|
879
|
+
if with_doc_processing:
|
880
|
+
logger.info(f"Document processing in Flows (Public Preview) has been enabled.")
|
848
881
|
|
849
882
|
@server_app.command(name="stop")
|
850
883
|
def server_stop(
|
@@ -854,6 +887,7 @@ def server_stop(
|
|
854
887
|
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
855
888
|
)
|
856
889
|
):
|
890
|
+
|
857
891
|
ensure_docker_installed()
|
858
892
|
default_env_path = get_default_env_file()
|
859
893
|
merged_env_dict = merge_env(
|
@@ -910,9 +944,24 @@ def server_logs(
|
|
910
944
|
def run_db_migration() -> None:
|
911
945
|
compose_path = get_compose_file()
|
912
946
|
compose_command = ensure_docker_compose_installed()
|
947
|
+
default_env_path = get_default_env_file()
|
948
|
+
merged_env_dict = merge_env(default_env_path, user_env_path=None)
|
949
|
+
merged_env_dict['WATSONX_SPACE_ID']='X'
|
950
|
+
merged_env_dict['WATSONX_APIKEY']='X'
|
951
|
+
merged_env_dict['WXAI_API_KEY'] = ''
|
952
|
+
merged_env_dict['ASSISTANT_EMBEDDINGS_API_KEY'] = ''
|
953
|
+
merged_env_dict['ASSISTANT_LLM_SPACE_ID'] = ''
|
954
|
+
merged_env_dict['ROUTING_LLM_SPACE_ID'] = ''
|
955
|
+
merged_env_dict['USE_SAAS_ML_TOOLS_RUNTIME'] = ''
|
956
|
+
merged_env_dict['BAM_API_KEY'] = ''
|
957
|
+
merged_env_dict['ASSISTANT_EMBEDDINGS_SPACE_ID'] = ''
|
958
|
+
merged_env_dict['ROUTING_LLM_API_KEY'] = ''
|
959
|
+
merged_env_dict['ASSISTANT_LLM_API_KEY'] = ''
|
960
|
+
final_env_file = write_merged_env_file(merged_env_dict)
|
913
961
|
|
914
962
|
command = compose_command + [
|
915
963
|
"-f", str(compose_path),
|
964
|
+
"--env-file", str(final_env_file),
|
916
965
|
"exec",
|
917
966
|
"wxo-server-db",
|
918
967
|
"bash",
|
@@ -47,7 +47,7 @@ def import_toolkit(
|
|
47
47
|
] = None,
|
48
48
|
tools: Annotated[
|
49
49
|
Optional[str],
|
50
|
-
typer.Option("--tools", "-t", help="Comma-separated list of tools to import. Or you can use
|
50
|
+
typer.Option("--tools", "-t", help="Comma-separated list of tools to import. Or you can use \"*\" to use all tools"),
|
51
51
|
] = None,
|
52
52
|
app_id: Annotated[
|
53
53
|
List[str],
|
@@ -41,7 +41,7 @@ from ibm_watsonx_orchestrate.client.connections import get_connections_client, g
|
|
41
41
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
|
42
42
|
from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_support_tools
|
43
43
|
from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
|
44
|
-
from ibm_watsonx_orchestrate.
|
44
|
+
from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
|
45
45
|
|
46
46
|
from ibm_watsonx_orchestrate import __version__
|
47
47
|
|
@@ -57,6 +57,12 @@ class ToolKind(str, Enum):
|
|
57
57
|
flow = "flow"
|
58
58
|
# skill = "skill"
|
59
59
|
|
60
|
+
def _get_connection_environments() -> List[ConnectionEnvironment]:
|
61
|
+
if is_local_dev():
|
62
|
+
return [ConnectionEnvironment.DRAFT]
|
63
|
+
else:
|
64
|
+
return [env.value for env in ConnectionEnvironment]
|
65
|
+
|
60
66
|
def validate_app_ids(kind: ToolKind, **args) -> None:
|
61
67
|
app_ids = args.get("app_id")
|
62
68
|
if not app_ids:
|
@@ -71,7 +77,14 @@ def validate_app_ids(kind: ToolKind, **args) -> None:
|
|
71
77
|
connections_client = get_connections_client()
|
72
78
|
|
73
79
|
imported_connections_list = connections_client.list()
|
74
|
-
imported_connections = {
|
80
|
+
imported_connections = {}
|
81
|
+
for conn in imported_connections_list:
|
82
|
+
app_id = conn.app_id
|
83
|
+
conn_env = conn.environment
|
84
|
+
if app_id in imported_connections:
|
85
|
+
imported_connections[app_id][conn_env] = conn
|
86
|
+
else:
|
87
|
+
imported_connections[app_id] = {conn_env: conn}
|
75
88
|
|
76
89
|
for app_id in app_ids:
|
77
90
|
if kind == ToolKind.python:
|
@@ -89,9 +102,29 @@ def validate_app_ids(kind: ToolKind, **args) -> None:
|
|
89
102
|
if app_id not in imported_connections:
|
90
103
|
logger.warning(f"No connection found for provided app-id '{app_id}'. Please create the connection using `orchestrate connections add`")
|
91
104
|
else:
|
92
|
-
|
93
|
-
|
94
|
-
|
105
|
+
# Validate that the connection is not key_value when the tool in openapi
|
106
|
+
if kind != ToolKind.openapi:
|
107
|
+
continue
|
108
|
+
|
109
|
+
environments = _get_connection_environments()
|
110
|
+
|
111
|
+
imported_connection = imported_connections.get(app_id)
|
112
|
+
|
113
|
+
for conn_environment in environments:
|
114
|
+
conn = imported_connection.get(conn_environment)
|
115
|
+
|
116
|
+
if conn is None or conn.security_scheme is None:
|
117
|
+
message = f"Connection '{app_id}' is not configured in the '{conn_environment}' environment."
|
118
|
+
if conn_environment == ConnectionEnvironment.DRAFT:
|
119
|
+
logger.error(message)
|
120
|
+
sys.exit(1)
|
121
|
+
else:
|
122
|
+
logger.warning(message + " If you deploy this tool without setting the live configuration the tool will error during execution.")
|
123
|
+
continue
|
124
|
+
|
125
|
+
if conn.security_scheme == ConnectionSecurityScheme.KEY_VALUE:
|
126
|
+
logger.error(f"Key value application connections can not be bound to an openapi tool")
|
127
|
+
exit(1)
|
95
128
|
|
96
129
|
def validate_params(kind: ToolKind, **args) -> None:
|
97
130
|
if kind in {"openapi", "python"} and args["file"] is None:
|
@@ -157,7 +190,14 @@ def validate_python_connections(tool: BaseTool):
|
|
157
190
|
|
158
191
|
provided_connections = list(connections.keys()) if connections else []
|
159
192
|
imported_connections_list = connections_client.list()
|
160
|
-
imported_connections = {
|
193
|
+
imported_connections = {}
|
194
|
+
for conn in imported_connections_list:
|
195
|
+
conn_id = conn.connection_id
|
196
|
+
conn_env = conn.environment
|
197
|
+
if conn_id in imported_connections:
|
198
|
+
imported_connections[conn_id][conn_env] = conn
|
199
|
+
else:
|
200
|
+
imported_connections[conn_id] = {conn_env: conn}
|
161
201
|
|
162
202
|
validation_failed = False
|
163
203
|
|
@@ -186,15 +226,30 @@ def validate_python_connections(tool: BaseTool):
|
|
186
226
|
|
187
227
|
connection_id = connections.get(sanatized_expected_tool_app_id)
|
188
228
|
imported_connection = imported_connections.get(connection_id)
|
189
|
-
imported_connection_auth_type = get_connection_type(security_scheme=imported_connection.security_scheme, auth_type=imported_connection.auth_type)
|
190
229
|
|
191
230
|
if connection_id and not imported_connection:
|
192
231
|
logger.error(f"The expected connection id '{connection_id}' does not match any known connection. This is likely caused by the connection being deleted. Please rec-reate the connection and re-import the tool")
|
193
232
|
validation_failed = True
|
233
|
+
|
234
|
+
environments = _get_connection_environments()
|
235
|
+
|
236
|
+
for conn_environment in environments:
|
237
|
+
conn = imported_connection.get(conn_environment)
|
238
|
+
conn_identifier = conn.app_id if conn is not None else connection_id
|
239
|
+
if conn is None or conn.security_scheme is None:
|
240
|
+
message = f"Connection '{conn_identifier}' is not configured in the '{conn_environment}' environment."
|
241
|
+
if conn_environment == ConnectionEnvironment.DRAFT:
|
242
|
+
logger.error(message)
|
243
|
+
sys.exit(1)
|
244
|
+
else:
|
245
|
+
logger.warning(message + " If you deploy this tool without setting the live configuration the tool will error during execution.")
|
246
|
+
continue
|
194
247
|
|
195
|
-
|
196
|
-
|
197
|
-
|
248
|
+
imported_connection_auth_type = get_connection_type(security_scheme=conn.security_scheme, auth_type=conn.auth_type)
|
249
|
+
|
250
|
+
if conn and len(expected_tool_conn_types) and imported_connection_auth_type not in expected_tool_conn_types:
|
251
|
+
logger.error(f"The app-id '{conn.app_id}' is of type '{imported_connection_auth_type.value}' in the '{conn_environment}' environment. The tool '{tool.__tool_spec__.name}' accepts connections of the following types '{', '.join(expected_tool_conn_types)}'. Use `orchestrate connections list` to view current connections and use `orchestrate connections add` to create the relevent connection")
|
252
|
+
validation_failed = True
|
198
253
|
|
199
254
|
if validation_failed:
|
200
255
|
exit(1)
|
@@ -345,7 +400,14 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
345
400
|
|
346
401
|
[bold cyan]Additional information:[/bold cyan]
|
347
402
|
|
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.
|
403
|
+
- 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].
|
404
|
+
|
405
|
+
[bold cyan]Experimental Features - Scheduling Flows and Agents: [/bold cyan]
|
406
|
+
- You can now schedule any Flows to be run on a later time. Just include the [bold green]"schedulable=True"[/bold green] attribute in the @flow decorator.
|
407
|
+
- Once enabled, you can schedule a flow by saying something like: [bold green]Can you schedule the flow <flow_name> to run everyday at 7am EST for 3 times?[/bold green]
|
408
|
+
- To schedule an agent, see the example in [bold green]examples/flow_builder/agent_scheduler[/bold green]. Use that to import the [bold green]agent_run[/bold green] tool to your agent.
|
409
|
+
- Use [bold green]agent_run[/bold green] tool to schedule an agent. For example: [bold green]Can you schedule the agent <agent_name> to run every weekday at 8am UK time?[/bold green]
|
410
|
+
- In scheduling, it is important to mention timezone or UTC time (also known as Greenwich Mean Time or Coordinated Universal Time) will be used.
|
349
411
|
|
350
412
|
"""
|
351
413
|
|
@@ -422,7 +484,7 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
|
|
422
484
|
permission="read_only",
|
423
485
|
flow_model=model)
|
424
486
|
|
425
|
-
tools = import_flow_support_tools()
|
487
|
+
tools = import_flow_support_tools(model=model)
|
426
488
|
|
427
489
|
tools.append(tool)
|
428
490
|
|
@@ -499,7 +561,7 @@ class ToolsController:
|
|
499
561
|
tools = []
|
500
562
|
logger.warning("Skill Import not implemented yet")
|
501
563
|
case _:
|
502
|
-
raise
|
564
|
+
raise BadRequest("Invalid kind selected")
|
503
565
|
|
504
566
|
for tool in tools:
|
505
567
|
yield tool
|
@@ -507,7 +569,20 @@ class ToolsController:
|
|
507
569
|
|
508
570
|
def list_tools(self, verbose=False):
|
509
571
|
response = self.get_client().get()
|
510
|
-
tool_specs = [
|
572
|
+
tool_specs = []
|
573
|
+
parse_errors = []
|
574
|
+
|
575
|
+
for tool in response:
|
576
|
+
try:
|
577
|
+
tool_specs.append(ToolSpec.model_validate(tool))
|
578
|
+
except Exception as e:
|
579
|
+
name = tool.get('name', None)
|
580
|
+
parse_errors.append([
|
581
|
+
f"Tool '{name}' could not be parsed",
|
582
|
+
json.dumps(tool),
|
583
|
+
e
|
584
|
+
])
|
585
|
+
|
511
586
|
tools = [BaseTool(spec=spec) for spec in tool_specs]
|
512
587
|
|
513
588
|
if verbose:
|
@@ -596,6 +671,10 @@ class ToolsController:
|
|
596
671
|
|
597
672
|
rich.print(table)
|
598
673
|
|
674
|
+
for error in parse_errors:
|
675
|
+
for l in error:
|
676
|
+
logger.error(l)
|
677
|
+
|
599
678
|
def get_all_tools(self) -> dict:
|
600
679
|
return {entry["name"]: entry["id"] for entry in self.get_client().get()}
|
601
680
|
|
@@ -6,6 +6,7 @@ from copy import deepcopy
|
|
6
6
|
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
7
7
|
from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
|
8
8
|
from enum import Enum
|
9
|
+
from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
|
9
10
|
|
10
11
|
# Section Headers
|
11
12
|
AUTH_SECTION_HEADER = "auth"
|
@@ -82,7 +83,6 @@ def _check_if_default_config_file(folder, file):
|
|
82
83
|
def _check_if_auth_config_file(folder, file):
|
83
84
|
return folder == AUTH_CONFIG_FILE_FOLDER and file == AUTH_CONFIG_FILE
|
84
85
|
|
85
|
-
|
86
86
|
def clear_protected_env_credentials_token():
|
87
87
|
auth_cfg = Config(config_file_folder=AUTH_CONFIG_FILE_FOLDER, config_file=AUTH_CONFIG_FILE)
|
88
88
|
auth_cfg.delete(AUTH_SECTION_HEADER, PROTECTED_ENV_NAME, AUTH_MCSP_TOKEN_OPT)
|
@@ -91,7 +91,7 @@ def clear_protected_env_credentials_token():
|
|
91
91
|
class ConfigFileTypes(str, Enum):
|
92
92
|
AUTH = 'auth'
|
93
93
|
CONFIG = 'config'
|
94
|
-
|
94
|
+
DOCPROC_FEATURE_CONF= 'docproc_feature'
|
95
95
|
|
96
96
|
class Config:
|
97
97
|
|
@@ -209,7 +209,7 @@ class Config:
|
|
209
209
|
as keys to access deeper sections of the config and then deleting the last specified key.
|
210
210
|
"""
|
211
211
|
if len(args) < 1:
|
212
|
-
raise
|
212
|
+
raise BadRequest("Config.delete() requires at least one positional argument")
|
213
213
|
|
214
214
|
config_data = {}
|
215
215
|
try:
|
@@ -4,6 +4,7 @@ from typing import Optional
|
|
4
4
|
from rich import print as pprint
|
5
5
|
from dotenv import dotenv_values
|
6
6
|
import typer
|
7
|
+
import sys
|
7
8
|
|
8
9
|
from ibm_watsonx_orchestrate.cli.config import Config, PYTHON_REGISTRY_HEADER, \
|
9
10
|
PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT
|
@@ -29,7 +30,6 @@ def version_callback(checkVersion: bool=True):
|
|
29
30
|
|
30
31
|
raise typer.Exit()
|
31
32
|
|
32
|
-
|
33
33
|
|
34
34
|
def init_callback(
|
35
35
|
ctx: typer.Context,
|
@@ -38,6 +38,15 @@ def init_callback(
|
|
38
38
|
"--version",
|
39
39
|
help="Show the installed version of the ADK and Developer Edition Tags",
|
40
40
|
callback=version_callback
|
41
|
+
),
|
42
|
+
debug: Optional[bool] = typer.Option(
|
43
|
+
False,
|
44
|
+
"--debug",
|
45
|
+
help="Enable debug mode"
|
41
46
|
)
|
42
47
|
):
|
48
|
+
if debug:
|
49
|
+
sys.tracebacklimit = 40
|
50
|
+
else:
|
51
|
+
sys.tracebacklimit = 0
|
43
52
|
pass
|
@@ -1,4 +1,6 @@
|
|
1
|
+
|
1
2
|
import typer
|
3
|
+
import sys
|
2
4
|
|
3
5
|
from ibm_watsonx_orchestrate.cli.commands.connections.connections_command import connections_app
|
4
6
|
from ibm_watsonx_orchestrate.cli.commands.login.login_command import login_app
|
@@ -13,6 +15,7 @@ from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import chann
|
|
13
15
|
from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
|
14
16
|
from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
|
15
17
|
from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_command import evaluation_app
|
18
|
+
from ibm_watsonx_orchestrate.cli.commands.copilot.copilot_command import copilot_app
|
16
19
|
from ibm_watsonx_orchestrate.cli.init_helper import init_callback
|
17
20
|
|
18
21
|
import urllib3
|
@@ -24,6 +27,7 @@ app = typer.Typer(
|
|
24
27
|
pretty_exceptions_enable=False,
|
25
28
|
callback=init_callback
|
26
29
|
)
|
30
|
+
|
27
31
|
app.add_typer(login_app)
|
28
32
|
app.add_typer(environment_app, name="env", help='Add, remove, or select the activate env other commands will interact with (either your local server or a production instance)')
|
29
33
|
app.add_typer(agents_app, name="agents", help='Interact with the agents in your active env')
|
@@ -36,6 +40,7 @@ app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Dev
|
|
36
40
|
app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
|
37
41
|
app.add_typer(channel_app, name="channels", help="Configure channels where your agent can exist on (such as embedded webchat)")
|
38
42
|
app.add_typer(evaluation_app, name="evaluations", help='Evaluate the performance of your agents in your active env')
|
43
|
+
app.add_typer(copilot_app, name="copilot", help='Access AI powered assistance to help refine your agents')
|
39
44
|
app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
|
40
45
|
|
41
46
|
if __name__ == "__main__":
|
@@ -3,6 +3,7 @@ import json
|
|
3
3
|
import requests
|
4
4
|
from abc import ABC, abstractmethod
|
5
5
|
from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
|
6
|
+
from typing_extensions import List
|
6
7
|
|
7
8
|
|
8
9
|
class ClientAPIException(requests.HTTPError):
|
@@ -62,6 +63,17 @@ class BaseAPIClient:
|
|
62
63
|
self._check_response(response)
|
63
64
|
return response.json() if response.text else {}
|
64
65
|
|
66
|
+
def _post_nd_json(self, path: str, data: dict = None, files: dict = None) -> List[dict]:
|
67
|
+
url = f"{self.base_url}{path}"
|
68
|
+
response = requests.post(url, headers=self._get_headers(), json=data, files=files)
|
69
|
+
self._check_response(response)
|
70
|
+
|
71
|
+
res = []
|
72
|
+
if response.text:
|
73
|
+
for line in response.text.splitlines():
|
74
|
+
res.append(json.loads(line))
|
75
|
+
return res
|
76
|
+
|
65
77
|
def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
|
66
78
|
url = f"{self.base_url}{path}"
|
67
79
|
# Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
|
@@ -63,12 +63,7 @@ class ConnectionsClient(BaseAPIClient):
|
|
63
63
|
# GET /api/v1/connections/applications/{app_id}
|
64
64
|
def get(self, app_id: str) -> GetConnectionResponse | None:
|
65
65
|
try:
|
66
|
-
|
67
|
-
f"/connections/applications/{app_id}"
|
68
|
-
if is_cpd_env(self.base_url)
|
69
|
-
else f"/connections/applications?app_id={app_id}"
|
70
|
-
)
|
71
|
-
return GetConnectionResponse.model_validate(self._get(path))
|
66
|
+
return GetConnectionResponse.model_validate(self._get(f"/connections/applications?app_id={app_id}"))
|
72
67
|
except ClientAPIException as e:
|
73
68
|
if e.response.status_code == 404:
|
74
69
|
return None
|
@@ -78,12 +73,7 @@ class ConnectionsClient(BaseAPIClient):
|
|
78
73
|
# GET api/v1/connections/applications
|
79
74
|
def list(self) -> List[ListConfigsResponse]:
|
80
75
|
try:
|
81
|
-
|
82
|
-
f"/connections/applications"
|
83
|
-
if is_cpd_env(self.base_url)
|
84
|
-
else f"/connections/applications?include_details=true"
|
85
|
-
)
|
86
|
-
res = self._get(path)
|
76
|
+
res = self._get(f"/connections/applications?include_details=true")
|
87
77
|
import json
|
88
78
|
json.dumps(res)
|
89
79
|
return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
|
@@ -135,19 +125,9 @@ class ConnectionsClient(BaseAPIClient):
|
|
135
125
|
def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> dict:
|
136
126
|
try:
|
137
127
|
if use_app_credentials:
|
138
|
-
|
139
|
-
f"/connections/applications/{app_id}/credentials?env={env}"
|
140
|
-
if is_cpd_env(self.base_url)
|
141
|
-
else f"/connections/applications/{app_id}/credentials/{env}"
|
142
|
-
)
|
143
|
-
return self._get(path)
|
128
|
+
return self._get(f"/connections/applications/{app_id}/credentials/{env}")
|
144
129
|
else:
|
145
|
-
|
146
|
-
f"/connections/applications/{app_id}/configs/runtime_credentials?env={env}"
|
147
|
-
if is_cpd_env(self.base_url)
|
148
|
-
else f"/connections/applications/runtime_credentials?app_id={app_id}&env={env}"
|
149
|
-
)
|
150
|
-
return self._get(path)
|
130
|
+
return self._get(f"/connections/applications/runtime_credentials?app_id={app_id}&env={env}")
|
151
131
|
except ClientAPIException as e:
|
152
132
|
if e.response.status_code == 404:
|
153
133
|
return None
|
@@ -177,12 +157,7 @@ class ConnectionsClient(BaseAPIClient):
|
|
177
157
|
if conn_id is None:
|
178
158
|
return ""
|
179
159
|
try:
|
180
|
-
|
181
|
-
f"/connections/applications/id/{conn_id}"
|
182
|
-
if is_cpd_env(self.base_url)
|
183
|
-
else f"/connections/applications?connection_id={conn_id}"
|
184
|
-
)
|
185
|
-
app_details = self._get(path)
|
160
|
+
app_details = self._get(f"/connections/applications?connection_id={conn_id}")
|
186
161
|
return app_details.get("app_id")
|
187
162
|
except ClientAPIException as e:
|
188
163
|
if e.response.status_code == 404:
|
@@ -0,0 +1,67 @@
|
|
1
|
+
from typing import Dict, Any
|
2
|
+
from uuid import uuid4
|
3
|
+
|
4
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
|
5
|
+
|
6
|
+
|
7
|
+
class CPEClient(BaseAPIClient):
|
8
|
+
"""
|
9
|
+
Client to handle CRUD operations for Conversational Prompt Engineering Service
|
10
|
+
"""
|
11
|
+
|
12
|
+
def __init__(self, *args, **kwargs):
|
13
|
+
self.chat_id = str(uuid4())
|
14
|
+
super().__init__(*args, **kwargs)
|
15
|
+
self.base_url = kwargs.get("base_url", self.base_url)
|
16
|
+
self.chat_model_name = 'llama-3-3-70b-instruct'
|
17
|
+
|
18
|
+
def _get_headers(self) -> dict:
|
19
|
+
return {
|
20
|
+
"chat_id": self.chat_id
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
def submit_pre_cpe_chat(self, user_message: str | None =None, tools: Dict[str, Any] = None, agents: Dict[str, Any] = None) -> dict:
|
25
|
+
payload = {
|
26
|
+
"message": user_message,
|
27
|
+
"tools": tools,
|
28
|
+
"agents": agents,
|
29
|
+
"chat_id": self.chat_id,
|
30
|
+
"chat_model_name": self.chat_model_name
|
31
|
+
}
|
32
|
+
|
33
|
+
response = self._post_nd_json("/wxo-cpe/create-agent", data=payload)
|
34
|
+
|
35
|
+
if response:
|
36
|
+
return response[-1]
|
37
|
+
|
38
|
+
|
39
|
+
def init_with_context(self, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
|
40
|
+
payload = {
|
41
|
+
"context_data": context_data,
|
42
|
+
"chat_id": self.chat_id
|
43
|
+
}
|
44
|
+
|
45
|
+
if model:
|
46
|
+
payload["target_model_name"] = model
|
47
|
+
|
48
|
+
response = self._post_nd_json("/wxo-cpe/init_cpe_from_wxo", data=payload)
|
49
|
+
|
50
|
+
if response:
|
51
|
+
return response[-1]
|
52
|
+
|
53
|
+
|
54
|
+
def invoke(self, prompt: str, model: str | None = None, context_data: Dict[str, Any] = None) -> dict:
|
55
|
+
payload = {
|
56
|
+
"prompt": prompt,
|
57
|
+
"context_data": context_data,
|
58
|
+
"chat_id": self.chat_id
|
59
|
+
}
|
60
|
+
|
61
|
+
if model:
|
62
|
+
payload["target_model_name"] = model
|
63
|
+
|
64
|
+
response = self._post_nd_json("/wxo-cpe/invoke", data=payload)
|
65
|
+
|
66
|
+
if response:
|
67
|
+
return response[-1]
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
|
1
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
2
2
|
import json
|
3
3
|
from typing_extensions import List
|
4
4
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
@@ -2,6 +2,8 @@ from ibm_watsonx_orchestrate.client.base_service_instance import BaseServiceInst
|
|
2
2
|
import logging
|
3
3
|
import requests
|
4
4
|
from ibm_watsonx_orchestrate.client.credentials import Credentials
|
5
|
+
import json
|
6
|
+
import base64
|
5
7
|
|
6
8
|
logger = logging.getLogger(__name__)
|
7
9
|
|
@@ -11,7 +13,7 @@ DEFAULT_TENANT = {
|
|
11
13
|
"tags": ["test"]
|
12
14
|
}
|
13
15
|
|
14
|
-
DEFAULT_USER =
|
16
|
+
DEFAULT_USER = json.loads(base64.b64decode('eyJ1c2VybmFtZSI6ICJ3eG8uYXJjaGVyQGlibS5jb20iLCJwYXNzd29yZCI6ICJ3YXRzb254In0='))
|
15
17
|
DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
|
16
18
|
DEFAULT_LOCAL_AUTH_ENDPOINT = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/auth/token"
|
17
19
|
DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/tenants"
|