ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.8.0b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ibm_watsonx_orchestrate/__init__.py +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 +26 -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 +5 -9
- 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/webchat/channels_webchat_controller.py +33 -23
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -4
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +293 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +154 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +33 -19
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +66 -9
- 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/copilot/cpe/copilot_cpe_client.py +66 -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 +48 -9
- ibm_watsonx_orchestrate/docker/compose-lite.yml +16 -4
- ibm_watsonx_orchestrate/docker/default.env +25 -15
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +3 -1
- 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 +91 -20
- ibm_watsonx_orchestrate/flow_builder/node.py +12 -1
- ibm_watsonx_orchestrate/flow_builder/types.py +169 -16
- 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.0b0.dist-info}/METADATA +5 -5
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/RECORD +56 -52
- 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.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -5,10 +5,12 @@ import os
|
|
5
5
|
import yaml
|
6
6
|
import csv
|
7
7
|
import rich
|
8
|
-
from pathlib import Path
|
9
8
|
import sys
|
10
|
-
|
9
|
+
import shutil
|
11
10
|
|
11
|
+
from rich.panel import Panel
|
12
|
+
from pathlib import Path
|
13
|
+
from dotenv import dotenv_values
|
12
14
|
from typing import Optional
|
13
15
|
from typing_extensions import Annotated
|
14
16
|
|
@@ -46,6 +48,32 @@ def validate_watsonx_credentials(user_env_file: str) -> bool:
|
|
46
48
|
os.environ.update({key: user_env[key] for key in required_keys})
|
47
49
|
logger.info("WatsonX credentials validated successfully.")
|
48
50
|
|
51
|
+
def read_csv(data_path: str, delimiter="\t"):
|
52
|
+
data = []
|
53
|
+
with open(data_path, "r") as f:
|
54
|
+
tsv_reader = csv.reader(f, delimiter=delimiter)
|
55
|
+
for line in tsv_reader:
|
56
|
+
data.append(line)
|
57
|
+
|
58
|
+
return data
|
59
|
+
|
60
|
+
def performance_test(agent_name, data_path, output_dir = None, user_env_file = None):
|
61
|
+
test_data = read_csv(data_path)
|
62
|
+
|
63
|
+
controller = EvaluationsController()
|
64
|
+
generated_performance_tests = controller.generate_performance_test(agent_name, test_data)
|
65
|
+
|
66
|
+
generated_perf_test_dir = Path(output_dir) / "generated_performance_tests"
|
67
|
+
generated_perf_test_dir.mkdir(exist_ok=True, parents=True)
|
68
|
+
|
69
|
+
for idx, test in enumerate(generated_performance_tests):
|
70
|
+
test_name = f"validate_external_agent_evaluation_test_{idx}.json"
|
71
|
+
with open(generated_perf_test_dir / test_name, encoding="utf-8", mode="w+") as f:
|
72
|
+
json.dump(test, f, indent=4)
|
73
|
+
|
74
|
+
rich.print(f"Performance test cases saved at path '{str(generated_perf_test_dir)}'")
|
75
|
+
rich.print("[gold3]Running Performance Test")
|
76
|
+
evaluate(output_dir=output_dir, test_paths=str(generated_perf_test_dir))
|
49
77
|
|
50
78
|
@evaluation_app.command(name="evaluate", help="Evaluate an agent against a set of test cases")
|
51
79
|
def evaluate(
|
@@ -115,7 +143,7 @@ def generate(
|
|
115
143
|
stories_path: Annotated[
|
116
144
|
str,
|
117
145
|
typer.Option(
|
118
|
-
"--
|
146
|
+
"--stories-path", "-s",
|
119
147
|
help="Path to the CSV file containing user stories for test case generation. "
|
120
148
|
"The file has 'story' and 'agent' columns."
|
121
149
|
)
|
@@ -123,14 +151,14 @@ def generate(
|
|
123
151
|
tools_path: Annotated[
|
124
152
|
str,
|
125
153
|
typer.Option(
|
126
|
-
"--
|
154
|
+
"--tools-path", "-t",
|
127
155
|
help="Path to the directory containing tool definitions."
|
128
156
|
)
|
129
157
|
],
|
130
158
|
output_dir: Annotated[
|
131
159
|
Optional[str],
|
132
160
|
typer.Option(
|
133
|
-
"--
|
161
|
+
"--output-dir", "-o",
|
134
162
|
help="Directory to save the generated test cases."
|
135
163
|
)
|
136
164
|
] = None,
|
@@ -151,7 +179,7 @@ def generate(
|
|
151
179
|
def analyze(data_path: Annotated[
|
152
180
|
str,
|
153
181
|
typer.Option(
|
154
|
-
"--
|
182
|
+
"--data-path", "-d",
|
155
183
|
help="Path to the directory that has the saved results"
|
156
184
|
)
|
157
185
|
],
|
@@ -167,30 +195,31 @@ def analyze(data_path: Annotated[
|
|
167
195
|
controller = EvaluationsController()
|
168
196
|
controller.analyze(data_path=data_path)
|
169
197
|
|
170
|
-
|
171
|
-
@evaluation_app.command(name="validate_external", help="Validate an external agent against a set of inputs")
|
198
|
+
@evaluation_app.command(name="validate-external", help="Validate an external agent against a set of inputs")
|
172
199
|
def validate_external(
|
173
200
|
data_path: Annotated[
|
174
201
|
str,
|
175
202
|
typer.Option(
|
176
|
-
"--
|
177
|
-
help="Path to .
|
203
|
+
"--tsv", "-t",
|
204
|
+
help="Path to .tsv file of inputs"
|
178
205
|
)
|
179
206
|
],
|
180
|
-
|
207
|
+
external_agent_config: Annotated[
|
181
208
|
str,
|
182
209
|
typer.Option(
|
183
|
-
"--config", "-
|
184
|
-
help="Path to the external agent yaml"
|
210
|
+
"--external-agent-config", "-ext",
|
211
|
+
help="Path to the external agent yaml",
|
212
|
+
|
185
213
|
)
|
186
214
|
],
|
187
215
|
credential: Annotated[
|
188
216
|
str,
|
189
217
|
typer.Option(
|
190
218
|
"--credential", "-crd",
|
191
|
-
help="credential string"
|
219
|
+
help="credential string",
|
220
|
+
rich_help_panel="Parameters for Validation"
|
192
221
|
)
|
193
|
-
],
|
222
|
+
] = None,
|
194
223
|
output_dir: Annotated[
|
195
224
|
str,
|
196
225
|
typer.Option(
|
@@ -204,21 +233,80 @@ def validate_external(
|
|
204
233
|
"--env-file", "-e",
|
205
234
|
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
206
235
|
),
|
236
|
+
] = None,
|
237
|
+
agent_name: Annotated[
|
238
|
+
str,
|
239
|
+
typer.Option(
|
240
|
+
"--agent_name", "-a",
|
241
|
+
help="Name of the native agent which has the external agent to test registered as a collaborater. See: https://developer.watson-orchestrate.ibm.com/agents/build_agent#native-agents)." \
|
242
|
+
" If this parameter is pased, validation of the external agent is not run.",
|
243
|
+
rich_help_panel="Parameters for Input Evaluation"
|
244
|
+
)
|
207
245
|
] = None
|
208
246
|
):
|
209
|
-
|
247
|
+
|
210
248
|
validate_watsonx_credentials(user_env_file)
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
249
|
+
Path(output_dir).mkdir(exist_ok=True)
|
250
|
+
shutil.copy(data_path, os.path.join(output_dir, "input_sample.tsv"))
|
251
|
+
|
252
|
+
if agent_name is not None:
|
253
|
+
eval_dir = os.path.join(output_dir, "evaluation")
|
254
|
+
if os.path.exists(eval_dir):
|
255
|
+
rich.print(f"[yellow]: found existing {eval_dir} in target directory. All content is removed.")
|
256
|
+
shutil.rmtree(os.path.join(output_dir, "evaluation"))
|
257
|
+
Path(eval_dir).mkdir(exist_ok=True)
|
258
|
+
# save external agent config even though its not used for evaluation
|
259
|
+
# it can help in later debugging customer agents
|
260
|
+
with open(os.path.join(eval_dir, "external_agent_cfg.yaml"), "w+") as f:
|
261
|
+
with open(external_agent_config, "r") as cfg:
|
262
|
+
external_agent_config = yaml.safe_load(cfg)
|
263
|
+
yaml.safe_dump(external_agent_config, f, indent=4)
|
264
|
+
|
265
|
+
rich.print(f"[gold3]Starting evaluation of inputs in '{data_path}' against '{agent_name}'[/gold3]")
|
266
|
+
performance_test(
|
267
|
+
agent_name=agent_name,
|
268
|
+
data_path=data_path,
|
269
|
+
output_dir=eval_dir,
|
270
|
+
user_env_file=user_env_file
|
271
|
+
)
|
272
|
+
|
273
|
+
else:
|
274
|
+
with open(external_agent_config, "r") as f:
|
275
|
+
external_agent_config = yaml.safe_load(f)
|
276
|
+
controller = EvaluationsController()
|
277
|
+
test_data = []
|
278
|
+
with open(data_path, "r") as f:
|
279
|
+
csv_reader = csv.reader(f, delimiter="\t")
|
280
|
+
for line in csv_reader:
|
281
|
+
test_data.append(line[0])
|
282
|
+
|
283
|
+
# save validation results in "validation_results" sub-dir
|
284
|
+
validation_folder = Path(output_dir) / "validation_results"
|
285
|
+
if os.path.exists(validation_folder):
|
286
|
+
rich.print(f"[yellow]: found existing {validation_folder} in target directory. All content is removed.")
|
287
|
+
shutil.rmtree(validation_folder)
|
288
|
+
validation_folder.mkdir(exist_ok=True, parents=True)
|
289
|
+
|
290
|
+
# validate the inputs in the provided csv file
|
291
|
+
summary = controller.external_validate(external_agent_config, test_data, credential)
|
292
|
+
with open(validation_folder / "validation_results.json", "w") as f:
|
293
|
+
json.dump(summary, f, indent=4)
|
294
|
+
|
295
|
+
# validate sample block inputs
|
296
|
+
rich.print("[gold3]Validating external agent to see if it can handle an array of messages.")
|
297
|
+
block_input_summary = controller.external_validate(external_agent_config, test_data, credential, add_context=True)
|
298
|
+
with open(validation_folder / "sample_block_validation_results.json", "w") as f:
|
299
|
+
json.dump(block_input_summary, f, indent=4)
|
300
|
+
|
301
|
+
user_validation_successful = all([item["success"] for item in summary])
|
302
|
+
block_validation_successful = all([item["success"] for item in block_input_summary])
|
303
|
+
|
304
|
+
if user_validation_successful and block_validation_successful:
|
305
|
+
msg = (
|
306
|
+
f"[green]Validation is successful. The result is saved to '{str(validation_folder)}'.[/green]\n"
|
307
|
+
"You can add the external agent as a collaborator agent. See: https://developer.watson-orchestrate.ibm.com/agents/build_agent#native-agents."
|
308
|
+
)
|
309
|
+
else:
|
310
|
+
msg = f"[dark_orange]Schema validation did not succeed. See '{str(validation_folder)}' for failures.[/dark_orange]"
|
311
|
+
|
312
|
+
rich.print(Panel(msg))
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
|
-
|
2
|
+
import os.path
|
3
|
+
from typing import List, Dict, Optional, Tuple
|
3
4
|
import csv
|
4
5
|
from pathlib import Path
|
5
6
|
import rich
|
@@ -10,12 +11,13 @@ from wxo_agentic_evaluation.batch_annotate import generate_test_cases_from_stori
|
|
10
11
|
from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig
|
11
12
|
from wxo_agentic_evaluation.record_chat import record_chats
|
12
13
|
from wxo_agentic_evaluation.external_agent.external_validate import ExternalAgentValidation
|
14
|
+
from wxo_agentic_evaluation.external_agent.performance_test import ExternalAgentPerformanceTest
|
13
15
|
from ibm_watsonx_orchestrate import __version__
|
14
16
|
from ibm_watsonx_orchestrate.cli.config import Config, ENV_WXO_URL_OPT, AUTH_CONFIG_FILE, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT
|
15
17
|
from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
|
16
18
|
from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import AgentsController
|
17
19
|
from ibm_watsonx_orchestrate.agent_builder.agents import AgentKind
|
18
|
-
|
20
|
+
import uuid
|
19
21
|
|
20
22
|
logger = logging.getLogger(__name__)
|
21
23
|
|
@@ -75,9 +77,13 @@ class EvaluationsController:
|
|
75
77
|
evaluate.main(config)
|
76
78
|
|
77
79
|
def record(self, output_dir) -> None:
|
80
|
+
|
81
|
+
|
82
|
+
random_uuid = str(uuid.uuid4())
|
83
|
+
|
78
84
|
url, tenant_name, token = self._get_env_config()
|
79
85
|
config_data = {
|
80
|
-
"output_dir": Path.cwd() if output_dir is None else Path(output_dir),
|
86
|
+
"output_dir": Path(os.path.join(Path.cwd(), random_uuid)) if output_dir is None else Path(os.path.join(output_dir,random_uuid)),
|
81
87
|
"service_url": url,
|
82
88
|
"tenant_name": tenant_name,
|
83
89
|
"token": token
|
@@ -143,16 +149,23 @@ class EvaluationsController:
|
|
143
149
|
def summarize(self) -> None:
|
144
150
|
pass
|
145
151
|
|
146
|
-
def external_validate(self, config: Dict, data: List[str], credential:str):
|
152
|
+
def external_validate(self, config: Dict, data: List[str], credential:str, add_context: bool = False):
|
147
153
|
validator = ExternalAgentValidation(credential=credential,
|
148
154
|
auth_scheme=config["auth_scheme"],
|
149
155
|
service_url=config["api_url"])
|
156
|
+
|
150
157
|
summary = []
|
151
158
|
for entry in data:
|
152
|
-
results = validator.call_validation(entry)
|
153
|
-
|
154
|
-
rich.print(f"[red] No events are generated for input {entry} [/red]")
|
155
|
-
summary.append({entry: results})
|
159
|
+
results = validator.call_validation(entry, add_context)
|
160
|
+
summary.append(results)
|
156
161
|
|
157
162
|
return summary
|
158
|
-
|
163
|
+
|
164
|
+
def generate_performance_test(self, agent_name: str, test_data: List[Tuple[str, str]]):
|
165
|
+
performance_test = ExternalAgentPerformanceTest(
|
166
|
+
agent_name=agent_name,
|
167
|
+
test_data=test_data
|
168
|
+
)
|
169
|
+
generated_performance_tests = performance_test.generate_tests()
|
170
|
+
|
171
|
+
return generated_performance_tests
|
@@ -21,24 +21,6 @@ def knowledge_base_import(
|
|
21
21
|
controller = KnowledgeBaseController()
|
22
22
|
controller.import_knowledge_base(file=file, app_id=app_id)
|
23
23
|
|
24
|
-
@knowledge_bases_app.command(name="patch", help="Patch a knowledge base by uploading documents, or providing an external vector index")
|
25
|
-
def knowledge_base_patch(
|
26
|
-
file: Annotated[
|
27
|
-
str,
|
28
|
-
typer.Option("--file", "-f", help="YAML or JSON file with knowledge base definition"),
|
29
|
-
],
|
30
|
-
name: Annotated[
|
31
|
-
str,
|
32
|
-
typer.Option("--name", "-n", help="Name of the knowledge base you wish to update"),
|
33
|
-
]=None,
|
34
|
-
id: Annotated[
|
35
|
-
str,
|
36
|
-
typer.Option("--id", "-i", help="ID of the knowledge base you wish to update"),
|
37
|
-
]=None
|
38
|
-
):
|
39
|
-
controller = KnowledgeBaseController()
|
40
|
-
controller.update_knowledge_base(id=id, name=name, file=file)
|
41
|
-
|
42
24
|
|
43
25
|
@knowledge_bases_app.command(name="list", help="List all knowledge bases")
|
44
26
|
def list_knowledge_bases(
|
@@ -8,7 +8,6 @@ import inspect
|
|
8
8
|
from pathlib import Path
|
9
9
|
from typing import List
|
10
10
|
|
11
|
-
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base_requests import KnowledgeBaseUpdateRequest
|
12
11
|
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import KnowledgeBase
|
13
12
|
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
|
14
13
|
from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
|
@@ -72,11 +71,20 @@ class KnowledgeBaseController:
|
|
72
71
|
client = self.get_client()
|
73
72
|
|
74
73
|
knowledge_bases = parse_file(file=file)
|
74
|
+
existing_knowledge_bases = client.get_by_names([kb.name for kb in knowledge_bases])
|
75
|
+
|
75
76
|
for kb in knowledge_bases:
|
76
77
|
try:
|
78
|
+
file_dir = "/".join(file.split("/")[:-1])
|
79
|
+
|
80
|
+
existing = list(filter(lambda ex: ex.get('name') == kb.name, existing_knowledge_bases))
|
81
|
+
if len(existing) > 0:
|
82
|
+
logger.info(f"Existing knowledge base '{kb.name}' found. Updating...")
|
83
|
+
self.update_knowledge_base(existing[0].get("id"), kb=kb, file_dir=file_dir)
|
84
|
+
continue
|
85
|
+
|
77
86
|
kb.validate_documents_or_index_exists()
|
78
87
|
if kb.documents:
|
79
|
-
file_dir = "/".join(file.split("/")[:-1])
|
80
88
|
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in kb.documents]
|
81
89
|
|
82
90
|
kb.prioritize_built_in_index = True
|
@@ -106,10 +114,7 @@ class KnowledgeBaseController:
|
|
106
114
|
|
107
115
|
logger.info(f"Successfully imported knowledge base '{kb.name}'")
|
108
116
|
except ClientAPIException as e:
|
109
|
-
|
110
|
-
logger.error(f"A knowledge base with the name '{kb.name}' already exists. Failed to import knowledge base")
|
111
|
-
else:
|
112
|
-
logger.error(f"Error importing knowledge base '{kb.name}\n' {e.response.text}")
|
117
|
+
logger.error(f"Error importing knowledge base '{kb.name}\n' {e.response.text}")
|
113
118
|
|
114
119
|
def get_id(
|
115
120
|
self, id: str, name: str
|
@@ -131,27 +136,36 @@ class KnowledgeBaseController:
|
|
131
136
|
|
132
137
|
|
133
138
|
def update_knowledge_base(
|
134
|
-
self,
|
139
|
+
self, knowledge_base_id: str, kb: KnowledgeBase, file_dir: str
|
135
140
|
) -> None:
|
136
|
-
|
137
|
-
|
141
|
+
filtered_files = []
|
142
|
+
|
143
|
+
if kb.documents:
|
144
|
+
status = self.get_client().status(knowledge_base_id)
|
145
|
+
existing_docs = [doc.get("metadata", {}).get("original_file_name", "") for doc in status.get("documents", [])]
|
146
|
+
|
147
|
+
for filepath in kb.documents:
|
148
|
+
filename = get_file_name(filepath)
|
138
149
|
|
139
|
-
|
140
|
-
|
141
|
-
|
150
|
+
if filename in existing_docs:
|
151
|
+
logger.warning(f'Document \"{filename}\" already exists in knowledge base, skipping.')
|
152
|
+
else:
|
153
|
+
filtered_files.append(filepath)
|
154
|
+
|
155
|
+
if filtered_files:
|
156
|
+
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in filtered_files]
|
142
157
|
|
143
|
-
|
144
|
-
payload =
|
158
|
+
kb.prioritize_built_in_index = True
|
159
|
+
payload = kb.model_dump(exclude_none=True);
|
145
160
|
payload.pop('documents');
|
146
161
|
|
147
162
|
self.get_client().update_with_documents(knowledge_base_id, payload=payload, files=files)
|
148
163
|
else:
|
149
|
-
if
|
150
|
-
|
151
|
-
self.get_client().update(knowledge_base_id,
|
164
|
+
if kb.conversational_search_tool and kb.conversational_search_tool.index_config:
|
165
|
+
kb.prioritize_built_in_index = False
|
166
|
+
self.get_client().update(knowledge_base_id, kb.model_dump(exclude_none=True))
|
152
167
|
|
153
|
-
|
154
|
-
logger.info(f"Successfully updated knowledge base {logEnding}")
|
168
|
+
logger.info(f"Knowledge base '{kb.name}' updated successfully")
|
155
169
|
|
156
170
|
|
157
171
|
def knowledge_base_status( self, id: str, name: str) -> None:
|
@@ -149,7 +149,7 @@ def models_policy_add(
|
|
149
149
|
retry_attempts: Annotated[
|
150
150
|
int,
|
151
151
|
typer.Option('--retry-attempts', help='The number of attempts to retry'),
|
152
|
-
],
|
152
|
+
] = None,
|
153
153
|
strategy_on_code: Annotated[
|
154
154
|
List[int],
|
155
155
|
typer.Option('--strategy-on-code', help='The http status to consider invoking the strategy'),
|
@@ -33,6 +33,29 @@ 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
|
+
|
50
|
+
current_config_file_values = cfg.get(USER_ENV_CACHE_HEADER)
|
51
|
+
current_config_file_values["SAAS_WDU_RUNTIME"] = value
|
52
|
+
|
53
|
+
cfg.save(
|
54
|
+
{
|
55
|
+
USER_ENV_CACHE_HEADER: current_config_file_values
|
56
|
+
}
|
57
|
+
)
|
58
|
+
|
36
59
|
def ensure_docker_installed() -> None:
|
37
60
|
try:
|
38
61
|
subprocess.run(["docker", "--version"], check=True, capture_output=True)
|
@@ -261,6 +284,13 @@ def _check_exclusive_observibility(langfuse_enabled: bool, ibm_tele_enabled: boo
|
|
261
284
|
return False
|
262
285
|
return True
|
263
286
|
|
287
|
+
def _prepare_clean_env(env_file: Path) -> None:
|
288
|
+
"""Remove env vars so terminal definitions don't override"""
|
289
|
+
keys_from_file = set(dotenv_values(str(env_file)).keys())
|
290
|
+
keys_to_unset = keys_from_file | _ALWAYS_UNSET
|
291
|
+
for key in keys_to_unset:
|
292
|
+
os.environ.pop(key, None)
|
293
|
+
|
264
294
|
def write_merged_env_file(merged_env: dict) -> Path:
|
265
295
|
tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
|
266
296
|
with tmp:
|
@@ -293,7 +323,8 @@ NON_SECRET_ENV_ITEMS = {
|
|
293
323
|
"WO_INSTANCE",
|
294
324
|
"USE_SAAS_ML_TOOLS_RUNTIME",
|
295
325
|
"AUTHORIZATION_URL",
|
296
|
-
"OPENSOURCE_REGISTRY_PROXY"
|
326
|
+
"OPENSOURCE_REGISTRY_PROXY",
|
327
|
+
"SAAS_WDU_RUNTIME"
|
297
328
|
}
|
298
329
|
def persist_user_env(env: dict, include_secrets: bool = False) -> None:
|
299
330
|
if include_secrets:
|
@@ -313,9 +344,10 @@ def get_persisted_user_env() -> dict | None:
|
|
313
344
|
user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
|
314
345
|
return user_env
|
315
346
|
|
316
|
-
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False,
|
347
|
+
def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_doc_processing=False) -> None:
|
317
348
|
compose_path = get_compose_file()
|
318
349
|
compose_command = ensure_docker_compose_installed()
|
350
|
+
_prepare_clean_env(final_env_file)
|
319
351
|
db_tag = read_env_file(final_env_file).get('DBTAG', None)
|
320
352
|
logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
|
321
353
|
|
@@ -345,7 +377,7 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
|
|
345
377
|
profiles.append("langfuse")
|
346
378
|
if experimental_with_ibm_telemetry:
|
347
379
|
profiles.append("ibm-telemetry")
|
348
|
-
if
|
380
|
+
if with_doc_processing:
|
349
381
|
profiles.append("docproc")
|
350
382
|
|
351
383
|
command = compose_command[:]
|
@@ -358,6 +390,8 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, exp
|
|
358
390
|
"up",
|
359
391
|
"--scale",
|
360
392
|
"ui=0",
|
393
|
+
"--scale",
|
394
|
+
"cpe=0",
|
361
395
|
"-d",
|
362
396
|
"--remove-orphans",
|
363
397
|
]
|
@@ -428,6 +462,7 @@ def wait_for_wxo_ui_health_check(timeout_seconds=45, interval_seconds=2):
|
|
428
462
|
def run_compose_lite_ui(user_env_file: Path) -> bool:
|
429
463
|
compose_path = get_compose_file()
|
430
464
|
compose_command = ensure_docker_compose_installed()
|
465
|
+
_prepare_clean_env(user_env_file)
|
431
466
|
ensure_docker_installed()
|
432
467
|
|
433
468
|
default_env = read_env_file(get_default_env_file())
|
@@ -522,6 +557,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
522
557
|
def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> None:
|
523
558
|
compose_path = get_compose_file()
|
524
559
|
compose_command = ensure_docker_compose_installed()
|
560
|
+
_prepare_clean_env(user_env_file)
|
525
561
|
|
526
562
|
|
527
563
|
ensure_docker_installed()
|
@@ -565,6 +601,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
|
|
565
601
|
def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
|
566
602
|
compose_path = get_compose_file()
|
567
603
|
compose_command = ensure_docker_compose_installed()
|
604
|
+
_prepare_clean_env(final_env_file)
|
568
605
|
|
569
606
|
command = compose_command + [
|
570
607
|
'--profile', '*',
|
@@ -597,6 +634,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
|
|
597
634
|
def run_compose_lite_logs(final_env_file: Path, is_reset: bool = False) -> None:
|
598
635
|
compose_path = get_compose_file()
|
599
636
|
compose_command = ensure_docker_compose_installed()
|
637
|
+
_prepare_clean_env(final_env_file)
|
600
638
|
|
601
639
|
command = compose_command + [
|
602
640
|
"-f", str(compose_path),
|
@@ -752,14 +790,16 @@ def server_start(
|
|
752
790
|
"--accept-terms-and-conditions",
|
753
791
|
help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
|
754
792
|
),
|
755
|
-
|
793
|
+
with_doc_processing: bool = typer.Option(
|
756
794
|
False,
|
757
|
-
'--with-
|
795
|
+
'--with-doc-processing', '-d',
|
758
796
|
help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
|
759
797
|
),
|
760
798
|
):
|
761
799
|
confirm_accepts_license_agreement(accept_terms_and_conditions)
|
762
800
|
|
801
|
+
define_saas_wdu_runtime()
|
802
|
+
|
763
803
|
if user_env_file and not Path(user_env_file).exists():
|
764
804
|
logger.error(f"Error: The specified environment file '{user_env_file}' does not exist.")
|
765
805
|
sys.exit(1)
|
@@ -795,8 +835,9 @@ def server_start(
|
|
795
835
|
if experimental_with_langfuse:
|
796
836
|
merged_env_dict['LANGFUSE_ENABLED'] = 'true'
|
797
837
|
|
798
|
-
if
|
838
|
+
if with_doc_processing:
|
799
839
|
merged_env_dict['DOCPROC_ENABLED'] = 'true'
|
840
|
+
define_saas_wdu_runtime("local")
|
800
841
|
|
801
842
|
if experimental_with_ibm_telemetry:
|
802
843
|
merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
|
@@ -815,8 +856,8 @@ def server_start(
|
|
815
856
|
run_compose_lite(final_env_file=final_env_file,
|
816
857
|
experimental_with_langfuse=experimental_with_langfuse,
|
817
858
|
experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
|
818
|
-
|
819
|
-
|
859
|
+
with_doc_processing=with_doc_processing)
|
860
|
+
|
820
861
|
run_db_migration()
|
821
862
|
|
822
863
|
logger.info("Waiting for orchestrate server to be fully initialized and ready...")
|
@@ -843,7 +884,7 @@ def server_start(
|
|
843
884
|
|
844
885
|
if experimental_with_langfuse:
|
845
886
|
logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
|
846
|
-
if
|
887
|
+
if with_doc_processing:
|
847
888
|
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.")
|
848
889
|
|
849
890
|
@server_app.command(name="stop")
|
@@ -854,6 +895,7 @@ def server_stop(
|
|
854
895
|
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
855
896
|
)
|
856
897
|
):
|
898
|
+
|
857
899
|
ensure_docker_installed()
|
858
900
|
default_env_path = get_default_env_file()
|
859
901
|
merged_env_dict = merge_env(
|
@@ -910,9 +952,24 @@ def server_logs(
|
|
910
952
|
def run_db_migration() -> None:
|
911
953
|
compose_path = get_compose_file()
|
912
954
|
compose_command = ensure_docker_compose_installed()
|
955
|
+
default_env_path = get_default_env_file()
|
956
|
+
merged_env_dict = merge_env(default_env_path, user_env_path=None)
|
957
|
+
merged_env_dict['WATSONX_SPACE_ID']='X'
|
958
|
+
merged_env_dict['WATSONX_APIKEY']='X'
|
959
|
+
merged_env_dict['WXAI_API_KEY'] = ''
|
960
|
+
merged_env_dict['ASSISTANT_EMBEDDINGS_API_KEY'] = ''
|
961
|
+
merged_env_dict['ASSISTANT_LLM_SPACE_ID'] = ''
|
962
|
+
merged_env_dict['ROUTING_LLM_SPACE_ID'] = ''
|
963
|
+
merged_env_dict['USE_SAAS_ML_TOOLS_RUNTIME'] = ''
|
964
|
+
merged_env_dict['BAM_API_KEY'] = ''
|
965
|
+
merged_env_dict['ASSISTANT_EMBEDDINGS_SPACE_ID'] = ''
|
966
|
+
merged_env_dict['ROUTING_LLM_API_KEY'] = ''
|
967
|
+
merged_env_dict['ASSISTANT_LLM_API_KEY'] = ''
|
968
|
+
final_env_file = write_merged_env_file(merged_env_dict)
|
913
969
|
|
914
970
|
command = compose_command + [
|
915
971
|
"-f", str(compose_path),
|
972
|
+
"--env-file", str(final_env_file),
|
916
973
|
"exec",
|
917
974
|
"wxo-server-db",
|
918
975
|
"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],
|