ibm-watsonx-orchestrate 1.8.0b1__py3-none-any.whl → 1.9.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/knowledge_bases/types.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/models/types.py +5 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +61 -11
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +18 -6
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +12 -5
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +15 -3
- ibm_watsonx_orchestrate/cli/commands/channels/types.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +2 -3
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -3
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +103 -23
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +86 -31
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +17 -13
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -37
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +4 -2
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -1
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +1 -1
- ibm_watsonx_orchestrate/client/connections/connections_client.py +19 -32
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +5 -3
- ibm_watsonx_orchestrate/client/utils.py +17 -16
- ibm_watsonx_orchestrate/docker/compose-lite.yml +127 -12
- ibm_watsonx_orchestrate/docker/default.env +26 -21
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +2 -2
- ibm_watsonx_orchestrate/flow_builder/flows/constants.py +2 -0
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +52 -10
- ibm_watsonx_orchestrate/flow_builder/node.py +34 -3
- ibm_watsonx_orchestrate/flow_builder/types.py +144 -26
- ibm_watsonx_orchestrate/flow_builder/utils.py +7 -5
- {ibm_watsonx_orchestrate-1.8.0b1.dist-info → ibm_watsonx_orchestrate-1.9.0.dist-info}/METADATA +1 -3
- {ibm_watsonx_orchestrate-1.8.0b1.dist-info → ibm_watsonx_orchestrate-1.9.0.dist-info}/RECORD +34 -35
- ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +0 -149
- {ibm_watsonx_orchestrate-1.8.0b1.dist-info → ibm_watsonx_orchestrate-1.9.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.8.0b1.dist-info → ibm_watsonx_orchestrate-1.9.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.8.0b1.dist-info → ibm_watsonx_orchestrate-1.9.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,11 +2,12 @@ import json
|
|
2
2
|
import logging
|
3
3
|
import typer
|
4
4
|
import os
|
5
|
-
import yaml
|
6
5
|
import csv
|
7
6
|
import rich
|
8
7
|
import sys
|
9
8
|
import shutil
|
9
|
+
import tempfile
|
10
|
+
import random
|
10
11
|
|
11
12
|
from rich.panel import Panel
|
12
13
|
from pathlib import Path
|
@@ -16,11 +17,37 @@ from typing_extensions import Annotated
|
|
16
17
|
|
17
18
|
from ibm_watsonx_orchestrate import __version__
|
18
19
|
from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_controller import EvaluationsController
|
20
|
+
from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import AgentsController
|
19
21
|
|
20
22
|
logger = logging.getLogger(__name__)
|
21
23
|
|
22
24
|
evaluation_app = typer.Typer(no_args_is_help=True)
|
23
25
|
|
26
|
+
def _native_agent_template():
|
27
|
+
return {
|
28
|
+
"spec_version": "v1",
|
29
|
+
"style": "default",
|
30
|
+
"llm": "watsonx/meta-llama/llama-3-405b-instruct",
|
31
|
+
"name": "",
|
32
|
+
"description": "Native agent for validating external agent",
|
33
|
+
"instructions": "Use the tools and external agent(s) provided to answer the user's question. If you do not have enough information to answer the question, say so. If you need more information, ask follow up questions.",
|
34
|
+
"collaborators": []
|
35
|
+
}
|
36
|
+
|
37
|
+
def _random_native_agent_name(external_agent_name):
|
38
|
+
""" Generate a native agent name in the following format to ensure uniqueness:
|
39
|
+
|
40
|
+
"external_agent_validation_{external_agent_name}_{random number}
|
41
|
+
|
42
|
+
So if the external agent name is, "QA_Agent", and the random number generated is, '100', the native agent name is:
|
43
|
+
"external_agent_validation_QA_Agent_100"
|
44
|
+
|
45
|
+
"""
|
46
|
+
seed = 42
|
47
|
+
random.seed(seed)
|
48
|
+
|
49
|
+
return f"external_agent_validation_{external_agent_name}_{random.randint(0, 100)}"
|
50
|
+
|
24
51
|
def read_env_file(env_path: Path|str) -> dict:
|
25
52
|
return dotenv_values(str(env_path))
|
26
53
|
|
@@ -218,7 +245,7 @@ def validate_external(
|
|
218
245
|
str,
|
219
246
|
typer.Option(
|
220
247
|
"--external-agent-config", "-ext",
|
221
|
-
help="Path to the external agent
|
248
|
+
help="Path to the external agent json file",
|
222
249
|
|
223
250
|
)
|
224
251
|
],
|
@@ -244,33 +271,65 @@ def validate_external(
|
|
244
271
|
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
245
272
|
),
|
246
273
|
] = None,
|
247
|
-
|
248
|
-
|
274
|
+
perf_test: Annotated[
|
275
|
+
bool,
|
249
276
|
typer.Option(
|
250
|
-
"--
|
251
|
-
help="
|
252
|
-
"
|
253
|
-
rich_help_panel="Parameters for Input Evaluation"
|
277
|
+
"--perf", "-p",
|
278
|
+
help="Performance test your external agent against the provide user stories.",
|
279
|
+
rich_help_panel="Parameters for Input Evaluation",
|
254
280
|
)
|
255
|
-
] =
|
281
|
+
] = False
|
256
282
|
):
|
257
283
|
|
258
284
|
validate_watsonx_credentials(user_env_file)
|
259
|
-
Path(output_dir).mkdir(exist_ok=True)
|
260
|
-
shutil.copy(data_path, os.path.join(output_dir, "input_sample.tsv"))
|
261
285
|
|
262
|
-
|
263
|
-
|
286
|
+
with open(external_agent_config, 'r') as f:
|
287
|
+
try:
|
288
|
+
external_agent_config = json.load(f)
|
289
|
+
except Exception:
|
290
|
+
rich.print(
|
291
|
+
f"[red]: Please provide a valid external agent spec in JSON format. See 'examples/evaluations/external_agent_validation/sample_external_agent_config.json' for an example."
|
292
|
+
)
|
293
|
+
sys.exit(1)
|
294
|
+
|
295
|
+
eval_dir = os.path.join(output_dir, "evaluations")
|
296
|
+
if perf_test:
|
264
297
|
if os.path.exists(eval_dir):
|
265
298
|
rich.print(f"[yellow]: found existing {eval_dir} in target directory. All content is removed.")
|
266
|
-
shutil.rmtree(
|
267
|
-
Path(eval_dir).mkdir(exist_ok=True)
|
299
|
+
shutil.rmtree(eval_dir)
|
300
|
+
Path(eval_dir).mkdir(exist_ok=True, parents=True)
|
268
301
|
# save external agent config even though its not used for evaluation
|
269
302
|
# it can help in later debugging customer agents
|
270
|
-
with open(os.path.join(eval_dir, "external_agent_cfg.
|
271
|
-
|
272
|
-
|
273
|
-
|
303
|
+
with open(os.path.join(eval_dir, f"external_agent_cfg.json"), "w+") as f:
|
304
|
+
json.dump(external_agent_config, f, indent=4)
|
305
|
+
|
306
|
+
logger.info("Registering External Agent")
|
307
|
+
agent_controller = AgentsController()
|
308
|
+
|
309
|
+
external_agent_config["title"] = external_agent_config["name"]
|
310
|
+
external_agent_config["auth_config"] = {"token": credential}
|
311
|
+
external_agent_config["spec_version"] = external_agent_config.get("spec_version", "v1")
|
312
|
+
external_agent_config["provider"] = "external_chat"
|
313
|
+
|
314
|
+
with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", suffix=".json", delete=True) as fp:
|
315
|
+
json.dump(external_agent_config, fp, indent=4)
|
316
|
+
fp.flush()
|
317
|
+
agents = agent_controller.import_agent(file=os.path.abspath(fp.name), app_id=None)
|
318
|
+
agent_controller.publish_or_update_agents(agents)
|
319
|
+
|
320
|
+
logger.info("Registering Native Agent")
|
321
|
+
|
322
|
+
native_agent_template = _native_agent_template()
|
323
|
+
agent_name = _random_native_agent_name(external_agent_config["name"])
|
324
|
+
rich.print(f"[blue][b]Generated native agent name is: [i]{agent_name}[/i][/b]")
|
325
|
+
native_agent_template["name"] = agent_name
|
326
|
+
native_agent_template["collaborators"] = [external_agent_config["name"]]
|
327
|
+
|
328
|
+
with tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", suffix=".json", delete=True) as fp:
|
329
|
+
json.dump(native_agent_template, fp, indent=4)
|
330
|
+
fp.flush()
|
331
|
+
agents = agent_controller.import_agent(file=os.path.abspath(fp.name), app_id=None)
|
332
|
+
agent_controller.publish_or_update_agents(agents)
|
274
333
|
|
275
334
|
rich.print(f"[gold3]Starting evaluation of inputs in '{data_path}' against '{agent_name}'[/gold3]")
|
276
335
|
performance_test(
|
@@ -281,8 +340,6 @@ def validate_external(
|
|
281
340
|
)
|
282
341
|
|
283
342
|
else:
|
284
|
-
with open(external_agent_config, "r") as f:
|
285
|
-
external_agent_config = yaml.safe_load(f)
|
286
343
|
controller = EvaluationsController()
|
287
344
|
test_data = []
|
288
345
|
with open(data_path, "r") as f:
|
@@ -290,31 +347,29 @@ def validate_external(
|
|
290
347
|
for line in csv_reader:
|
291
348
|
test_data.append(line[0])
|
292
349
|
|
293
|
-
# save validation results in "
|
294
|
-
validation_folder = Path(output_dir) / "
|
350
|
+
# save validation results in "validate_external" sub-dir
|
351
|
+
validation_folder = Path(output_dir) / "validate_external"
|
295
352
|
if os.path.exists(validation_folder):
|
296
353
|
rich.print(f"[yellow]: found existing {validation_folder} in target directory. All content is removed.")
|
297
354
|
shutil.rmtree(validation_folder)
|
298
355
|
validation_folder.mkdir(exist_ok=True, parents=True)
|
356
|
+
shutil.copy(data_path, os.path.join(validation_folder, "input_sample.tsv"))
|
299
357
|
|
300
358
|
# validate the inputs in the provided csv file
|
301
359
|
summary = controller.external_validate(external_agent_config, test_data, credential)
|
302
|
-
with open(validation_folder / "validation_results.json", "w") as f:
|
303
|
-
json.dump(summary, f, indent=4)
|
304
|
-
|
305
360
|
# validate sample block inputs
|
306
|
-
rich.print("[gold3]Validating external agent
|
361
|
+
rich.print("[gold3]Validating external agent against an array of messages.")
|
307
362
|
block_input_summary = controller.external_validate(external_agent_config, test_data, credential, add_context=True)
|
308
|
-
|
309
|
-
|
310
|
-
|
363
|
+
|
364
|
+
with open(validation_folder / "validation_results.json", "w") as f:
|
365
|
+
json.dump([summary, block_input_summary], f, indent=4)
|
366
|
+
|
311
367
|
user_validation_successful = all([item["success"] for item in summary])
|
312
368
|
block_validation_successful = all([item["success"] for item in block_input_summary])
|
313
369
|
|
314
370
|
if user_validation_successful and block_validation_successful:
|
315
371
|
msg = (
|
316
372
|
f"[green]Validation is successful. The result is saved to '{str(validation_folder)}'.[/green]\n"
|
317
|
-
"You can add the external agent as a collaborator agent. See: https://developer.watson-orchestrate.ibm.com/agents/build_agent#native-agents."
|
318
373
|
)
|
319
374
|
else:
|
320
375
|
msg = f"[dark_orange]Schema validation did not succeed. See '{str(validation_folder)}' for failures.[/dark_orange]"
|
@@ -10,19 +10,23 @@ _BASIC_PROVIDER_CONFIG_KEYS = {'provider', 'api_key', 'custom_host', 'url_to_fet
|
|
10
10
|
|
11
11
|
PROVIDER_EXTRA_PROPERTIES_LUT = {
|
12
12
|
ModelProvider.ANTHROPIC: {'anthropic_beta', 'anthropic_version'},
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
13
|
+
ModelProvider.AZURE_AI: {
|
14
|
+
'azure_resource_name',
|
15
|
+
'azure_deployment_id',
|
16
|
+
'azure_api_version',
|
17
|
+
'ad_auth',
|
18
|
+
'azure_auth_mode',
|
19
|
+
'azure_managed_client_id',
|
20
|
+
'azure_entra_client_id',
|
21
|
+
'azure_entra_client_secret',
|
22
|
+
'azure_entra_tenant_id',
|
23
|
+
'azure_ad_token',
|
24
|
+
'azure_model_name',
|
25
|
+
'azure_inference_deployment_name',
|
26
|
+
'azure_inference_api_version',
|
27
|
+
'azure_inference_extra_params',
|
28
|
+
'azure_inference_foundry_url'
|
29
|
+
},
|
26
30
|
ModelProvider.AZURE_OPENAI: {
|
27
31
|
'azure_resource_name',
|
28
32
|
'azure_deployment_id',
|
@@ -167,15 +167,12 @@ class ModelsController:
|
|
167
167
|
logger.error("Error: WATSONX_URL is required in the environment.")
|
168
168
|
sys.exit(1)
|
169
169
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
else:
|
174
|
-
logger.info("Retrieving virtual-model models list...")
|
175
|
-
virtual_models = models_client.list()
|
170
|
+
|
171
|
+
logger.info("Retrieving virtual-model models list...")
|
172
|
+
virtual_models = models_client.list()
|
176
173
|
|
177
|
-
|
178
|
-
|
174
|
+
logger.info("Retrieving virtual-policies models list...")
|
175
|
+
virtual_model_policies = model_policies_client.list()
|
179
176
|
|
180
177
|
logger.info("Retrieving watsonx.ai models list...")
|
181
178
|
found_models = _get_wxai_foundational_models()
|
@@ -4,11 +4,13 @@ import os
|
|
4
4
|
import platform
|
5
5
|
import subprocess
|
6
6
|
import sys
|
7
|
+
import shutil
|
7
8
|
import tempfile
|
8
9
|
import time
|
9
10
|
from pathlib import Path
|
10
11
|
from urllib.parse import urlparse
|
11
12
|
|
13
|
+
import re
|
12
14
|
import jwt
|
13
15
|
import requests
|
14
16
|
import typer
|
@@ -32,6 +34,13 @@ logger = logging.getLogger(__name__)
|
|
32
34
|
|
33
35
|
server_app = typer.Typer(no_args_is_help=True)
|
34
36
|
|
37
|
+
_EXPORT_FILE_TYPES: set[str] = {
|
38
|
+
'py',
|
39
|
+
'yaml',
|
40
|
+
'yml',
|
41
|
+
'json',
|
42
|
+
'env'
|
43
|
+
}
|
35
44
|
|
36
45
|
_ALWAYS_UNSET: set[str] = {
|
37
46
|
"WO_API_KEY",
|
@@ -43,11 +52,34 @@ _ALWAYS_UNSET: set[str] = {
|
|
43
52
|
"WO_USERNAME",
|
44
53
|
"WO_PASSWORD",
|
45
54
|
}
|
55
|
+
|
56
|
+
NON_SECRET_ENV_ITEMS: set[str] = {
|
57
|
+
"WO_DEVELOPER_EDITION_SOURCE",
|
58
|
+
"WO_INSTANCE",
|
59
|
+
"USE_SAAS_ML_TOOLS_RUNTIME",
|
60
|
+
"AUTHORIZATION_URL",
|
61
|
+
"OPENSOURCE_REGISTRY_PROXY",
|
62
|
+
"SAAS_WDU_RUNTIME",
|
63
|
+
"LATEST_ENV_FILE",
|
64
|
+
}
|
46
65
|
|
47
66
|
def define_saas_wdu_runtime(value: str = "none") -> None:
|
48
67
|
cfg = Config()
|
49
68
|
cfg.write(USER_ENV_CACHE_HEADER,"SAAS_WDU_RUNTIME",value)
|
50
69
|
|
70
|
+
def set_compose_file_path_in_env(path: str = None) -> None:
|
71
|
+
Config().save(
|
72
|
+
{
|
73
|
+
USER_ENV_CACHE_HEADER: {
|
74
|
+
"DOCKER_COMPOSE_FILE_PATH" : path
|
75
|
+
}
|
76
|
+
}
|
77
|
+
)
|
78
|
+
|
79
|
+
def get_compose_file_path_from_env() -> str:
|
80
|
+
return Config().read(USER_ENV_CACHE_HEADER,"DOCKER_COMPOSE_FILE_PATH")
|
81
|
+
|
82
|
+
|
51
83
|
def ensure_docker_installed() -> None:
|
52
84
|
try:
|
53
85
|
subprocess.run(["docker", "--version"], check=True, capture_output=True)
|
@@ -106,6 +138,11 @@ def docker_login_by_dev_edition_source(env_dict: dict, source: str) -> None:
|
|
106
138
|
|
107
139
|
|
108
140
|
def get_compose_file() -> Path:
|
141
|
+
custom_compose_path = get_compose_file_path_from_env()
|
142
|
+
return Path(custom_compose_path) if custom_compose_path else get_default_compose_file()
|
143
|
+
|
144
|
+
|
145
|
+
def get_default_compose_file() -> Path:
|
109
146
|
with resources.as_file(
|
110
147
|
resources.files("ibm_watsonx_orchestrate.docker").joinpath("compose-lite.yml")
|
111
148
|
) as compose_file:
|
@@ -283,12 +320,17 @@ def _prepare_clean_env(env_file: Path) -> None:
|
|
283
320
|
for key in keys_to_unset:
|
284
321
|
os.environ.pop(key, None)
|
285
322
|
|
286
|
-
def write_merged_env_file(merged_env: dict) -> Path:
|
287
|
-
|
288
|
-
|
323
|
+
def write_merged_env_file(merged_env: dict, target_path: str = None) -> Path:
|
324
|
+
|
325
|
+
if target_path:
|
326
|
+
file = open(target_path,"w")
|
327
|
+
else:
|
328
|
+
file = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
|
329
|
+
|
330
|
+
with file:
|
289
331
|
for key, val in merged_env.items():
|
290
|
-
|
291
|
-
return Path(
|
332
|
+
file.write(f"{key}={val}\n")
|
333
|
+
return Path(file.name)
|
292
334
|
|
293
335
|
|
294
336
|
def get_dbtag_from_architecture(merged_env_dict: dict) -> str:
|
@@ -310,14 +352,6 @@ def refresh_local_credentials() -> None:
|
|
310
352
|
clear_protected_env_credentials_token()
|
311
353
|
_login(name=PROTECTED_ENV_NAME, apikey=None)
|
312
354
|
|
313
|
-
NON_SECRET_ENV_ITEMS = {
|
314
|
-
"WO_DEVELOPER_EDITION_SOURCE",
|
315
|
-
"WO_INSTANCE",
|
316
|
-
"USE_SAAS_ML_TOOLS_RUNTIME",
|
317
|
-
"AUTHORIZATION_URL",
|
318
|
-
"OPENSOURCE_REGISTRY_PROXY",
|
319
|
-
"SAAS_WDU_RUNTIME"
|
320
|
-
}
|
321
355
|
def persist_user_env(env: dict, include_secrets: bool = False) -> None:
|
322
356
|
if include_secrets:
|
323
357
|
persistable_env = env
|
@@ -337,7 +371,10 @@ def get_persisted_user_env() -> dict | None:
|
|
337
371
|
return user_env
|
338
372
|
|
339
373
|
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_doc_processing=False) -> None:
|
374
|
+
|
375
|
+
|
340
376
|
compose_path = get_compose_file()
|
377
|
+
|
341
378
|
compose_command = ensure_docker_compose_installed()
|
342
379
|
_prepare_clean_env(final_env_file)
|
343
380
|
db_tag = read_env_file(final_env_file).get('DBTAG', None)
|
@@ -754,6 +791,32 @@ def auto_configure_callback_ip(merged_env_dict: dict) -> dict:
|
|
754
791
|
|
755
792
|
return merged_env_dict
|
756
793
|
|
794
|
+
def prepare_server_env_vars(user_env: dict = {}):
|
795
|
+
|
796
|
+
default_env = read_env_file(get_default_env_file())
|
797
|
+
dev_edition_source = get_dev_edition_source(user_env)
|
798
|
+
default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, user_env, source=dev_edition_source)
|
799
|
+
|
800
|
+
# Update the default environment with the default registry variables only if they are not already set
|
801
|
+
for key in default_registry_vars:
|
802
|
+
if key not in default_env or not default_env[key]:
|
803
|
+
default_env[key] = default_registry_vars[key]
|
804
|
+
|
805
|
+
# Merge the default environment with the user environment
|
806
|
+
merged_env_dict = {
|
807
|
+
**default_env,
|
808
|
+
**user_env,
|
809
|
+
}
|
810
|
+
|
811
|
+
merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
|
812
|
+
|
813
|
+
# Auto-configure callback IP for async tools
|
814
|
+
merged_env_dict = auto_configure_callback_ip(merged_env_dict)
|
815
|
+
|
816
|
+
apply_llm_api_key_defaults(merged_env_dict)
|
817
|
+
|
818
|
+
return merged_env_dict
|
819
|
+
|
757
820
|
@server_app.command(name="start")
|
758
821
|
def server_start(
|
759
822
|
user_env_file: str = typer.Option(
|
@@ -787,38 +850,37 @@ def server_start(
|
|
787
850
|
'--with-doc-processing', '-d',
|
788
851
|
help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
|
789
852
|
),
|
853
|
+
custom_compose_file: str = typer.Option(
|
854
|
+
None,
|
855
|
+
'--compose-file', '-f',
|
856
|
+
help='Provide the path to a custom docker-compose file to use instead of the default compose file'
|
857
|
+
),
|
790
858
|
):
|
791
859
|
confirm_accepts_license_agreement(accept_terms_and_conditions)
|
792
860
|
|
793
861
|
define_saas_wdu_runtime()
|
794
862
|
|
863
|
+
ensure_docker_installed()
|
864
|
+
|
795
865
|
if user_env_file and not Path(user_env_file).exists():
|
796
|
-
logger.error(f"
|
866
|
+
logger.error(f"The specified environment file '{user_env_file}' does not exist.")
|
797
867
|
sys.exit(1)
|
798
|
-
ensure_docker_installed()
|
799
868
|
|
800
|
-
|
869
|
+
if custom_compose_file:
|
870
|
+
if Path(custom_compose_file).exists():
|
871
|
+
logger.warning("You are using a custom docker compose file, official support will not be available for this configuration")
|
872
|
+
else:
|
873
|
+
logger.error(f"The specified docker-compose file '{custom_compose_file}' does not exist.")
|
874
|
+
sys.exit(1)
|
875
|
+
|
876
|
+
#Run regardless, to allow this to set compose as 'None' when not in use
|
877
|
+
set_compose_file_path_in_env(custom_compose_file)
|
878
|
+
|
801
879
|
user_env = read_env_file(user_env_file) if user_env_file else {}
|
802
880
|
persist_user_env(user_env, include_secrets=persist_env_secrets)
|
803
881
|
|
804
|
-
|
805
|
-
default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, user_env, source=dev_edition_source)
|
806
|
-
|
807
|
-
# Update the default environment with the default registry variables only if they are not already set
|
808
|
-
for key in default_registry_vars:
|
809
|
-
if key not in default_env or not default_env[key]:
|
810
|
-
default_env[key] = default_registry_vars[key]
|
882
|
+
merged_env_dict = prepare_server_env_vars(user_env)
|
811
883
|
|
812
|
-
# Merge the default environment with the user environment
|
813
|
-
merged_env_dict = {
|
814
|
-
**default_env,
|
815
|
-
**user_env,
|
816
|
-
}
|
817
|
-
|
818
|
-
merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
|
819
|
-
|
820
|
-
# Auto-configure callback IP for async tools
|
821
|
-
merged_env_dict = auto_configure_callback_ip(merged_env_dict)
|
822
884
|
if not _check_exclusive_observibility(experimental_with_langfuse, experimental_with_ibm_telemetry):
|
823
885
|
logger.error("Please select either langfuse or ibm telemetry for observability not both")
|
824
886
|
sys.exit(1)
|
@@ -835,14 +897,12 @@ def server_start(
|
|
835
897
|
merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
|
836
898
|
|
837
899
|
try:
|
900
|
+
dev_edition_source = get_dev_edition_source(merged_env_dict)
|
838
901
|
docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
|
839
902
|
except ValueError as e:
|
840
903
|
logger.error(f"Error: {e}")
|
841
904
|
sys.exit(1)
|
842
905
|
|
843
|
-
apply_llm_api_key_defaults(merged_env_dict)
|
844
|
-
|
845
|
-
|
846
906
|
final_env_file = write_merged_env_file(merged_env_dict)
|
847
907
|
|
848
908
|
run_compose_lite(final_env_file=final_env_file,
|
@@ -877,7 +937,7 @@ def server_start(
|
|
877
937
|
if experimental_with_langfuse:
|
878
938
|
logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
|
879
939
|
if with_doc_processing:
|
880
|
-
logger.info(f"Document processing
|
940
|
+
logger.info(f"Document processing in Flows (Public Preview) has been enabled.")
|
881
941
|
|
882
942
|
@server_app.command(name="stop")
|
883
943
|
def server_stop(
|
@@ -1000,5 +1060,55 @@ def run_db_migration() -> None:
|
|
1000
1060
|
)
|
1001
1061
|
sys.exit(1)
|
1002
1062
|
|
1063
|
+
|
1064
|
+
def bump_file_iteration(filename: str) -> str:
|
1065
|
+
regex = re.compile(f"^(?P<name>[^\\(\\s\\.\\)]+)(\\((?P<num>\\d+)\\))?(?P<type>\\.(?:{'|'.join(_EXPORT_FILE_TYPES)}))?$")
|
1066
|
+
_m = regex.match(filename)
|
1067
|
+
iter = int(_m['num']) + 1 if (_m and _m['num']) else 1
|
1068
|
+
return f"{_m['name']}({iter}){_m['type'] or ''}"
|
1069
|
+
|
1070
|
+
def get_next_free_file_iteration(filename: str) -> str:
|
1071
|
+
while Path(filename).exists():
|
1072
|
+
filename = bump_file_iteration(filename)
|
1073
|
+
return filename
|
1074
|
+
|
1075
|
+
@server_app.command(name="eject", help="output the docker-compose file and associated env file used to run the server")
|
1076
|
+
def server_eject(
|
1077
|
+
user_env_file: str = typer.Option(
|
1078
|
+
None,
|
1079
|
+
"--env-file",
|
1080
|
+
"-e",
|
1081
|
+
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
1082
|
+
)
|
1083
|
+
):
|
1084
|
+
|
1085
|
+
if not user_env_file:
|
1086
|
+
logger.error(f"To use 'server eject' you need to specify an env file with '--env-file' or '-e'")
|
1087
|
+
sys.exit(1)
|
1088
|
+
|
1089
|
+
if not Path(user_env_file).exists():
|
1090
|
+
logger.error(f"The specified environment file '{user_env_file}' does not exist.")
|
1091
|
+
sys.exit(1)
|
1092
|
+
|
1093
|
+
logger.warning("Changes to your docker compose file are not supported")
|
1094
|
+
|
1095
|
+
compose_file_path = get_compose_file()
|
1096
|
+
|
1097
|
+
compose_output_file = get_next_free_file_iteration('docker-compose.yml')
|
1098
|
+
logger.info(f"Exporting docker compose file to '{compose_output_file}'")
|
1099
|
+
|
1100
|
+
shutil.copyfile(compose_file_path,compose_output_file)
|
1101
|
+
|
1102
|
+
|
1103
|
+
user_env = read_env_file(user_env_file)
|
1104
|
+
merged_env_dict = prepare_server_env_vars(user_env)
|
1105
|
+
|
1106
|
+
env_output_file = get_next_free_file_iteration('server.env')
|
1107
|
+
logger.info(f"Exporting env file to '{env_output_file}'")
|
1108
|
+
|
1109
|
+
write_merged_env_file(merged_env=merged_env_dict,target_path=env_output_file)
|
1110
|
+
|
1111
|
+
logger.info(f"To make use of the exported configuration file run \"orchestrate server start -e {env_output_file} -f {compose_output_file}\"")
|
1112
|
+
|
1003
1113
|
if __name__ == "__main__":
|
1004
1114
|
server_app()
|
@@ -64,8 +64,8 @@ def import_toolkit(
|
|
64
64
|
else:
|
65
65
|
tool_list = None
|
66
66
|
|
67
|
-
if not package and not package_root:
|
68
|
-
logger.error("You must provide either '--package'
|
67
|
+
if not package and not package_root and not command:
|
68
|
+
logger.error("You must provide either '--package', '--package-root' or '--command'.")
|
69
69
|
sys.exit(1)
|
70
70
|
|
71
71
|
if package_root and not command:
|
@@ -85,6 +85,8 @@ def import_toolkit(
|
|
85
85
|
else:
|
86
86
|
logger.error("Unable to infer start up command: '--language' flag must be either 'node' or 'python' when using the '--package' flag without '--command' flag.")
|
87
87
|
sys.exit(1)
|
88
|
+
else:
|
89
|
+
logger.warning(f"Default package installation command for package '{package}' overridden by '--command {command}'.")
|
88
90
|
|
89
91
|
|
90
92
|
toolkit_controller = ToolkitController(
|
@@ -70,7 +70,7 @@ class ToolkitController:
|
|
70
70
|
self.client = None
|
71
71
|
|
72
72
|
self.source: ToolkitSource = (
|
73
|
-
ToolkitSource.
|
73
|
+
ToolkitSource.FILES if package_root else ToolkitSource.PUBLIC_REGISTRY
|
74
74
|
)
|
75
75
|
|
76
76
|
def get_client(self) -> ToolKitClient:
|
@@ -105,6 +105,14 @@ class ToolkitController:
|
|
105
105
|
command = command_parts[0]
|
106
106
|
args = command_parts[1:]
|
107
107
|
|
108
|
+
if self.package_root:
|
109
|
+
is_folder = os.path.isdir(self.package_root)
|
110
|
+
is_zip_file = os.path.isfile(self.package_root) and zipfile.is_zipfile(self.package_root)
|
111
|
+
|
112
|
+
if not is_folder and not is_zip_file:
|
113
|
+
logger.error(f"Unable to find a valid directory or zip file at location '{self.package_root}'")
|
114
|
+
sys.exit(1)
|
115
|
+
|
108
116
|
console = Console()
|
109
117
|
|
110
118
|
with tempfile.TemporaryDirectory() as tmpdir:
|
@@ -590,7 +590,7 @@ class ToolsController:
|
|
590
590
|
for tool in tools:
|
591
591
|
tools_list.append(json.loads(tool.dumps_spec()))
|
592
592
|
|
593
|
-
rich.
|
593
|
+
rich.print_json(json.dumps(tools_list, indent=4))
|
594
594
|
else:
|
595
595
|
table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
|
596
596
|
column_args = {
|