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
@@ -23,7 +23,7 @@ from ibm_watsonx_orchestrate.cli.config import (
|
|
23
23
|
)
|
24
24
|
from ibm_watsonx_orchestrate.client.client import Client
|
25
25
|
from ibm_watsonx_orchestrate.client.client_errors import ClientError
|
26
|
-
from ibm_watsonx_orchestrate.client.
|
26
|
+
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient, ClientAPIException
|
27
27
|
from ibm_watsonx_orchestrate.client.credentials import Credentials
|
28
28
|
from threading import Lock
|
29
29
|
from ibm_watsonx_orchestrate.client.utils import is_local_dev, check_token_validity, is_cpd_env
|
@@ -55,13 +55,13 @@ def _validate_token_functionality(token: str, url: str) -> None:
|
|
55
55
|
'''
|
56
56
|
is_cpd = is_cpd_env(url)
|
57
57
|
if is_cpd is True:
|
58
|
-
|
58
|
+
knowledge_base_client = KnowledgeBaseClient(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
|
59
59
|
else:
|
60
|
-
|
61
|
-
|
60
|
+
knowledge_base_client = KnowledgeBaseClient(base_url=url, api_key=token, is_local=is_local_dev(url))
|
61
|
+
knowledge_base_client.api_key = token
|
62
62
|
|
63
63
|
try:
|
64
|
-
|
64
|
+
knowledge_base_client.get()
|
65
65
|
except ClientAPIException as e:
|
66
66
|
if e.response.status_code >= 400:
|
67
67
|
reason = e.response.reason
|
@@ -167,7 +167,7 @@ def activate(name: str, apikey: str=None, username: str=None, password: str=None
|
|
167
167
|
|
168
168
|
def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, type: EnvironmentAuthType=None, insecure: bool=None, verify: str=None) -> None:
|
169
169
|
if name == PROTECTED_ENV_NAME:
|
170
|
-
logger.error(f"The name '{PROTECTED_ENV_NAME}' is a reserved environment name. Please select a
|
170
|
+
logger.error(f"The name '{PROTECTED_ENV_NAME}' is a reserved environment name. Please select a different name or use `orchestrate env activate {PROTECTED_ENV_NAME}` to swap to '{PROTECTED_ENV_NAME}'")
|
171
171
|
return
|
172
172
|
|
173
173
|
cfg = Config()
|
@@ -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
|
|
@@ -23,14 +25,20 @@ def read_env_file(env_path: Path|str) -> dict:
|
|
23
25
|
return dotenv_values(str(env_path))
|
24
26
|
|
25
27
|
def validate_watsonx_credentials(user_env_file: str) -> bool:
|
26
|
-
|
28
|
+
required_sets = [
|
29
|
+
["WATSONX_SPACE_ID", "WATSONX_APIKEY"],
|
30
|
+
["WO_INSTANCE", "WO_API_KEY"]
|
31
|
+
]
|
27
32
|
|
28
|
-
|
33
|
+
def has_valid_keys(env: dict) -> bool:
|
34
|
+
return any(all(key in env for key in key_set) for key_set in required_sets)
|
35
|
+
|
36
|
+
if has_valid_keys(os.environ):
|
29
37
|
logger.info("WatsonX credentials validated successfully.")
|
30
38
|
return
|
31
39
|
|
32
40
|
if user_env_file is None:
|
33
|
-
logger.error("WatsonX credentials are not set. Please set WATSONX_SPACE_ID and WATSONX_APIKEY in your system environment variables or include them in your
|
41
|
+
logger.error("WatsonX credentials are not set. Please set either WATSONX_SPACE_ID and WATSONX_APIKEY or WO_INSTANCE and WO_API_KEY in your system environment variables or include them in your environment file and pass it with --env-file option.")
|
34
42
|
sys.exit(1)
|
35
43
|
|
36
44
|
if not Path(user_env_file).exists():
|
@@ -39,13 +47,43 @@ def validate_watsonx_credentials(user_env_file: str) -> bool:
|
|
39
47
|
|
40
48
|
user_env = read_env_file(user_env_file)
|
41
49
|
|
42
|
-
if not
|
43
|
-
logger.error("Error: The environment file does not contain the required keys: WATSONX_SPACE_ID and WATSONX_APIKEY.")
|
50
|
+
if not has_valid_keys(user_env):
|
51
|
+
logger.error("Error: The environment file does not contain the required keys: either WATSONX_SPACE_ID and WATSONX_APIKEY or WO_INSTANCE and WO_API_KEY.")
|
44
52
|
sys.exit(1)
|
45
53
|
|
46
|
-
os.environ
|
54
|
+
# Update os.environ with whichever set is present
|
55
|
+
for key_set in required_sets:
|
56
|
+
if all(key in user_env for key in key_set):
|
57
|
+
os.environ.update({key: user_env[key] for key in key_set})
|
58
|
+
break
|
47
59
|
logger.info("WatsonX credentials validated successfully.")
|
48
60
|
|
61
|
+
def read_csv(data_path: str, delimiter="\t"):
|
62
|
+
data = []
|
63
|
+
with open(data_path, "r") as f:
|
64
|
+
tsv_reader = csv.reader(f, delimiter=delimiter)
|
65
|
+
for line in tsv_reader:
|
66
|
+
data.append(line)
|
67
|
+
|
68
|
+
return data
|
69
|
+
|
70
|
+
def performance_test(agent_name, data_path, output_dir = None, user_env_file = None):
|
71
|
+
test_data = read_csv(data_path)
|
72
|
+
|
73
|
+
controller = EvaluationsController()
|
74
|
+
generated_performance_tests = controller.generate_performance_test(agent_name, test_data)
|
75
|
+
|
76
|
+
generated_perf_test_dir = Path(output_dir) / "generated_performance_tests"
|
77
|
+
generated_perf_test_dir.mkdir(exist_ok=True, parents=True)
|
78
|
+
|
79
|
+
for idx, test in enumerate(generated_performance_tests):
|
80
|
+
test_name = f"validate_external_agent_evaluation_test_{idx}.json"
|
81
|
+
with open(generated_perf_test_dir / test_name, encoding="utf-8", mode="w+") as f:
|
82
|
+
json.dump(test, f, indent=4)
|
83
|
+
|
84
|
+
rich.print(f"Performance test cases saved at path '{str(generated_perf_test_dir)}'")
|
85
|
+
rich.print("[gold3]Running Performance Test")
|
86
|
+
evaluate(output_dir=output_dir, test_paths=str(generated_perf_test_dir))
|
49
87
|
|
50
88
|
@evaluation_app.command(name="evaluate", help="Evaluate an agent against a set of test cases")
|
51
89
|
def evaluate(
|
@@ -115,7 +153,7 @@ def generate(
|
|
115
153
|
stories_path: Annotated[
|
116
154
|
str,
|
117
155
|
typer.Option(
|
118
|
-
"--
|
156
|
+
"--stories-path", "-s",
|
119
157
|
help="Path to the CSV file containing user stories for test case generation. "
|
120
158
|
"The file has 'story' and 'agent' columns."
|
121
159
|
)
|
@@ -123,14 +161,14 @@ def generate(
|
|
123
161
|
tools_path: Annotated[
|
124
162
|
str,
|
125
163
|
typer.Option(
|
126
|
-
"--
|
164
|
+
"--tools-path", "-t",
|
127
165
|
help="Path to the directory containing tool definitions."
|
128
166
|
)
|
129
167
|
],
|
130
168
|
output_dir: Annotated[
|
131
169
|
Optional[str],
|
132
170
|
typer.Option(
|
133
|
-
"--
|
171
|
+
"--output-dir", "-o",
|
134
172
|
help="Directory to save the generated test cases."
|
135
173
|
)
|
136
174
|
] = None,
|
@@ -151,7 +189,7 @@ def generate(
|
|
151
189
|
def analyze(data_path: Annotated[
|
152
190
|
str,
|
153
191
|
typer.Option(
|
154
|
-
"--
|
192
|
+
"--data-path", "-d",
|
155
193
|
help="Path to the directory that has the saved results"
|
156
194
|
)
|
157
195
|
],
|
@@ -167,30 +205,31 @@ def analyze(data_path: Annotated[
|
|
167
205
|
controller = EvaluationsController()
|
168
206
|
controller.analyze(data_path=data_path)
|
169
207
|
|
170
|
-
|
171
|
-
@evaluation_app.command(name="validate_external", help="Validate an external agent against a set of inputs")
|
208
|
+
@evaluation_app.command(name="validate-external", help="Validate an external agent against a set of inputs")
|
172
209
|
def validate_external(
|
173
210
|
data_path: Annotated[
|
174
211
|
str,
|
175
212
|
typer.Option(
|
176
|
-
"--
|
177
|
-
help="Path to .
|
213
|
+
"--tsv", "-t",
|
214
|
+
help="Path to .tsv file of inputs"
|
178
215
|
)
|
179
216
|
],
|
180
|
-
|
217
|
+
external_agent_config: Annotated[
|
181
218
|
str,
|
182
219
|
typer.Option(
|
183
|
-
"--config", "-
|
184
|
-
help="Path to the external agent yaml"
|
220
|
+
"--external-agent-config", "-ext",
|
221
|
+
help="Path to the external agent yaml",
|
222
|
+
|
185
223
|
)
|
186
224
|
],
|
187
225
|
credential: Annotated[
|
188
226
|
str,
|
189
227
|
typer.Option(
|
190
228
|
"--credential", "-crd",
|
191
|
-
help="credential string"
|
229
|
+
help="credential string",
|
230
|
+
rich_help_panel="Parameters for Validation"
|
192
231
|
)
|
193
|
-
],
|
232
|
+
] = None,
|
194
233
|
output_dir: Annotated[
|
195
234
|
str,
|
196
235
|
typer.Option(
|
@@ -204,21 +243,80 @@ def validate_external(
|
|
204
243
|
"--env-file", "-e",
|
205
244
|
help="Path to a .env file that overrides default.env. Then environment variables override both."
|
206
245
|
),
|
246
|
+
] = None,
|
247
|
+
agent_name: Annotated[
|
248
|
+
str,
|
249
|
+
typer.Option(
|
250
|
+
"--agent_name", "-a",
|
251
|
+
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)." \
|
252
|
+
" If this parameter is pased, validation of the external agent is not run.",
|
253
|
+
rich_help_panel="Parameters for Input Evaluation"
|
254
|
+
)
|
207
255
|
] = None
|
208
256
|
):
|
209
|
-
|
257
|
+
|
210
258
|
validate_watsonx_credentials(user_env_file)
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
259
|
+
Path(output_dir).mkdir(exist_ok=True)
|
260
|
+
shutil.copy(data_path, os.path.join(output_dir, "input_sample.tsv"))
|
261
|
+
|
262
|
+
if agent_name is not None:
|
263
|
+
eval_dir = os.path.join(output_dir, "evaluation")
|
264
|
+
if os.path.exists(eval_dir):
|
265
|
+
rich.print(f"[yellow]: found existing {eval_dir} in target directory. All content is removed.")
|
266
|
+
shutil.rmtree(os.path.join(output_dir, "evaluation"))
|
267
|
+
Path(eval_dir).mkdir(exist_ok=True)
|
268
|
+
# save external agent config even though its not used for evaluation
|
269
|
+
# it can help in later debugging customer agents
|
270
|
+
with open(os.path.join(eval_dir, "external_agent_cfg.yaml"), "w+") as f:
|
271
|
+
with open(external_agent_config, "r") as cfg:
|
272
|
+
external_agent_config = yaml.safe_load(cfg)
|
273
|
+
yaml.safe_dump(external_agent_config, f, indent=4)
|
274
|
+
|
275
|
+
rich.print(f"[gold3]Starting evaluation of inputs in '{data_path}' against '{agent_name}'[/gold3]")
|
276
|
+
performance_test(
|
277
|
+
agent_name=agent_name,
|
278
|
+
data_path=data_path,
|
279
|
+
output_dir=eval_dir,
|
280
|
+
user_env_file=user_env_file
|
281
|
+
)
|
282
|
+
|
283
|
+
else:
|
284
|
+
with open(external_agent_config, "r") as f:
|
285
|
+
external_agent_config = yaml.safe_load(f)
|
286
|
+
controller = EvaluationsController()
|
287
|
+
test_data = []
|
288
|
+
with open(data_path, "r") as f:
|
289
|
+
csv_reader = csv.reader(f, delimiter="\t")
|
290
|
+
for line in csv_reader:
|
291
|
+
test_data.append(line[0])
|
292
|
+
|
293
|
+
# save validation results in "validation_results" sub-dir
|
294
|
+
validation_folder = Path(output_dir) / "validation_results"
|
295
|
+
if os.path.exists(validation_folder):
|
296
|
+
rich.print(f"[yellow]: found existing {validation_folder} in target directory. All content is removed.")
|
297
|
+
shutil.rmtree(validation_folder)
|
298
|
+
validation_folder.mkdir(exist_ok=True, parents=True)
|
299
|
+
|
300
|
+
# validate the inputs in the provided csv file
|
301
|
+
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
|
+
# validate sample block inputs
|
306
|
+
rich.print("[gold3]Validating external agent to see if it can handle an array of messages.")
|
307
|
+
block_input_summary = controller.external_validate(external_agent_config, test_data, credential, add_context=True)
|
308
|
+
with open(validation_folder / "sample_block_validation_results.json", "w") as f:
|
309
|
+
json.dump(block_input_summary, f, indent=4)
|
310
|
+
|
311
|
+
user_validation_successful = all([item["success"] for item in summary])
|
312
|
+
block_validation_successful = all([item["success"] for item in block_input_summary])
|
313
|
+
|
314
|
+
if user_validation_successful and block_validation_successful:
|
315
|
+
msg = (
|
316
|
+
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
|
+
)
|
319
|
+
else:
|
320
|
+
msg = f"[dark_orange]Schema validation did not succeed. See '{str(validation_folder)}' for failures.[/dark_orange]"
|
321
|
+
|
322
|
+
rich.print(Panel(msg))
|
@@ -1,21 +1,23 @@
|
|
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
|
-
import
|
6
|
+
import sys
|
6
7
|
from wxo_agentic_evaluation import main as evaluate
|
7
8
|
from wxo_agentic_evaluation.tool_planner import build_snapshot
|
8
9
|
from wxo_agentic_evaluation.analyze_run import analyze
|
9
10
|
from wxo_agentic_evaluation.batch_annotate import generate_test_cases_from_stories
|
10
|
-
from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig
|
11
|
+
from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig, ProviderConfig
|
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
|
|
@@ -39,12 +41,26 @@ class EvaluationsController:
|
|
39
41
|
def evaluate(self, config_file: Optional[str] = None, test_paths: Optional[str] = None, output_dir: Optional[str] = None) -> None:
|
40
42
|
url, tenant_name, token = self._get_env_config()
|
41
43
|
|
44
|
+
if "WATSONX_SPACE_ID" in os.environ and "WATSONX_APIKEY" in os.environ:
|
45
|
+
provider = "watsonx"
|
46
|
+
elif "WO_INSTANCE" in os.environ and "WO_API_KEY" in os.environ:
|
47
|
+
provider = "model_proxy"
|
48
|
+
else:
|
49
|
+
logger.error(
|
50
|
+
"No provider found. Please either provide a config_file or set either WATSONX_SPACE_ID and WATSONX_APIKEY or WO_INSTANCE and WO_API_KEY in your system environment variables."
|
51
|
+
)
|
52
|
+
sys.exit(1)
|
53
|
+
|
42
54
|
config_data = {
|
43
55
|
"wxo_lite_version": __version__,
|
44
56
|
"auth_config": AuthConfig(
|
45
57
|
url=url,
|
46
58
|
tenant_name=tenant_name,
|
47
59
|
token=token
|
60
|
+
),
|
61
|
+
"provider_config": ProviderConfig(
|
62
|
+
provider=provider,
|
63
|
+
model_id="meta-llama/llama-3-405b-instruct",
|
48
64
|
)
|
49
65
|
}
|
50
66
|
|
@@ -60,6 +76,10 @@ class EvaluationsController:
|
|
60
76
|
if "llm_user_config" in file_config:
|
61
77
|
llm_config_data = file_config.pop("llm_user_config")
|
62
78
|
config_data["llm_user_config"] = LLMUserConfig(**llm_config_data)
|
79
|
+
|
80
|
+
if "provider_config" in file_config:
|
81
|
+
provider_config_data = file_config.pop("provider_config")
|
82
|
+
config_data["provider_config"] = ProviderConfig(**provider_config_data)
|
63
83
|
|
64
84
|
config_data.update(file_config)
|
65
85
|
|
@@ -75,9 +95,13 @@ class EvaluationsController:
|
|
75
95
|
evaluate.main(config)
|
76
96
|
|
77
97
|
def record(self, output_dir) -> None:
|
98
|
+
|
99
|
+
|
100
|
+
random_uuid = str(uuid.uuid4())
|
101
|
+
|
78
102
|
url, tenant_name, token = self._get_env_config()
|
79
103
|
config_data = {
|
80
|
-
"output_dir": Path.cwd() if output_dir is None else Path(output_dir),
|
104
|
+
"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
105
|
"service_url": url,
|
82
106
|
"tenant_name": tenant_name,
|
83
107
|
"token": token
|
@@ -143,16 +167,23 @@ class EvaluationsController:
|
|
143
167
|
def summarize(self) -> None:
|
144
168
|
pass
|
145
169
|
|
146
|
-
def external_validate(self, config: Dict, data: List[str], credential:str):
|
170
|
+
def external_validate(self, config: Dict, data: List[str], credential:str, add_context: bool = False):
|
147
171
|
validator = ExternalAgentValidation(credential=credential,
|
148
172
|
auth_scheme=config["auth_scheme"],
|
149
173
|
service_url=config["api_url"])
|
174
|
+
|
150
175
|
summary = []
|
151
176
|
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})
|
177
|
+
results = validator.call_validation(entry, add_context)
|
178
|
+
summary.append(results)
|
156
179
|
|
157
180
|
return summary
|
158
|
-
|
181
|
+
|
182
|
+
def generate_performance_test(self, agent_name: str, test_data: List[Tuple[str, str]]):
|
183
|
+
performance_test = ExternalAgentPerformanceTest(
|
184
|
+
agent_name=agent_name,
|
185
|
+
test_data=test_data
|
186
|
+
)
|
187
|
+
generated_performance_tests = performance_test.generate_tests()
|
188
|
+
|
189
|
+
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,21 @@ class KnowledgeBaseController:
|
|
72
71
|
client = self.get_client()
|
73
72
|
|
74
73
|
knowledge_bases = parse_file(file=file)
|
74
|
+
|
75
|
+
existing_knowledge_bases = client.get_by_names([kb.name for kb in knowledge_bases])
|
76
|
+
|
75
77
|
for kb in knowledge_bases:
|
76
78
|
try:
|
79
|
+
file_dir = "/".join(file.split("/")[:-1])
|
80
|
+
|
81
|
+
existing = list(filter(lambda ex: ex.get('name') == kb.name, existing_knowledge_bases))
|
82
|
+
if len(existing) > 0:
|
83
|
+
logger.info(f"Existing knowledge base '{kb.name}' found. Updating...")
|
84
|
+
self.update_knowledge_base(existing[0].get("id"), kb=kb, file_dir=file_dir)
|
85
|
+
continue
|
86
|
+
|
77
87
|
kb.validate_documents_or_index_exists()
|
78
88
|
if kb.documents:
|
79
|
-
file_dir = "/".join(file.split("/")[:-1])
|
80
89
|
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in kb.documents]
|
81
90
|
|
82
91
|
kb.prioritize_built_in_index = True
|
@@ -106,10 +115,7 @@ class KnowledgeBaseController:
|
|
106
115
|
|
107
116
|
logger.info(f"Successfully imported knowledge base '{kb.name}'")
|
108
117
|
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}")
|
118
|
+
logger.error(f"Error importing knowledge base '{kb.name}\n' {e.response.text}")
|
113
119
|
|
114
120
|
def get_id(
|
115
121
|
self, id: str, name: str
|
@@ -131,27 +137,37 @@ class KnowledgeBaseController:
|
|
131
137
|
|
132
138
|
|
133
139
|
def update_knowledge_base(
|
134
|
-
self,
|
135
|
-
) -> None:
|
136
|
-
|
137
|
-
|
140
|
+
self, knowledge_base_id: str, kb: KnowledgeBase, file_dir: str
|
141
|
+
) -> None:
|
142
|
+
if kb.documents:
|
143
|
+
status = self.get_client().status(knowledge_base_id)
|
144
|
+
existing_docs = [doc.get("metadata", {}).get("original_file_name", "") for doc in status.get("documents", [])]
|
145
|
+
|
146
|
+
removed_docs = existing_docs[:]
|
147
|
+
for filepath in kb.documents:
|
148
|
+
filename = get_file_name(filepath)
|
149
|
+
|
150
|
+
if filename in existing_docs:
|
151
|
+
logger.warning(f'Document \"{filename}\" already exists in knowledge base. Updating...')
|
152
|
+
removed_docs.remove(filename)
|
153
|
+
|
154
|
+
for filename in removed_docs:
|
155
|
+
logger.warning(f'Document \"{filename}\" removed from knowledge base.')
|
138
156
|
|
139
|
-
|
140
|
-
|
141
|
-
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in update_request.documents]
|
157
|
+
|
158
|
+
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in kb.documents]
|
142
159
|
|
143
|
-
|
144
|
-
payload =
|
160
|
+
kb.prioritize_built_in_index = True
|
161
|
+
payload = kb.model_dump(exclude_none=True);
|
145
162
|
payload.pop('documents');
|
146
163
|
|
147
164
|
self.get_client().update_with_documents(knowledge_base_id, payload=payload, files=files)
|
148
165
|
else:
|
149
|
-
if
|
150
|
-
|
151
|
-
self.get_client().update(knowledge_base_id,
|
166
|
+
if kb.conversational_search_tool and kb.conversational_search_tool.index_config:
|
167
|
+
kb.prioritize_built_in_index = False
|
168
|
+
self.get_client().update(knowledge_base_id, kb.model_dump(exclude_none=True))
|
152
169
|
|
153
|
-
|
154
|
-
logger.info(f"Successfully updated knowledge base {logEnding}")
|
170
|
+
logger.info(f"Knowledge base '{kb.name}' updated successfully")
|
155
171
|
|
156
172
|
|
157
173
|
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'),
|
@@ -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()
|