ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.6.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.
Files changed (71) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
  3. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
  4. ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
  5. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
  7. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
  8. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
  9. ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
  10. ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
  11. ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
  13. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
  14. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
  15. ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
  17. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
  18. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
  19. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  20. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
  21. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
  29. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
  30. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
  31. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
  32. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
  33. ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
  36. ibm_watsonx_orchestrate/cli/config.py +2 -0
  37. ibm_watsonx_orchestrate/cli/main.py +6 -0
  38. ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
  39. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
  40. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
  41. ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
  42. ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
  43. ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
  44. ibm_watsonx_orchestrate/client/credentials.py +4 -0
  45. ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
  46. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
  47. ibm_watsonx_orchestrate/client/service_instance.py +42 -1
  48. ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
  49. ibm_watsonx_orchestrate/client/utils.py +37 -2
  50. ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
  51. ibm_watsonx_orchestrate/docker/default.env +40 -15
  52. ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
  53. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
  54. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
  55. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
  56. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
  57. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
  58. ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
  59. ibm_watsonx_orchestrate/run/connections.py +4 -4
  60. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
  61. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
  62. ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
  63. ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
  64. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
  65. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
  66. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
  67. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
  68. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
  69. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
  70. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
  71. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,312 @@
1
+ import json
2
+ import logging
3
+ import typer
4
+ import os
5
+ import yaml
6
+ import csv
7
+ import rich
8
+ import sys
9
+ import shutil
10
+
11
+ from rich.panel import Panel
12
+ from pathlib import Path
13
+ from dotenv import dotenv_values
14
+ from typing import Optional
15
+ from typing_extensions import Annotated
16
+
17
+ from ibm_watsonx_orchestrate import __version__
18
+ from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_controller import EvaluationsController
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ evaluation_app = typer.Typer(no_args_is_help=True)
23
+
24
+ def read_env_file(env_path: Path|str) -> dict:
25
+ return dotenv_values(str(env_path))
26
+
27
+ def validate_watsonx_credentials(user_env_file: str) -> bool:
28
+ required_keys = ["WATSONX_SPACE_ID", "WATSONX_APIKEY"]
29
+
30
+ if all(key in os.environ for key in required_keys):
31
+ logger.info("WatsonX credentials validated successfully.")
32
+ return
33
+
34
+ if user_env_file is None:
35
+ 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 enviroment file and pass it with --env-file option.")
36
+ sys.exit(1)
37
+
38
+ if not Path(user_env_file).exists():
39
+ logger.error(f"Error: The specified environment file '{user_env_file}' does not exist.")
40
+ sys.exit(1)
41
+
42
+ user_env = read_env_file(user_env_file)
43
+
44
+ if not all(key in user_env for key in required_keys):
45
+ logger.error("Error: The environment file does not contain the required keys: WATSONX_SPACE_ID and WATSONX_APIKEY.")
46
+ sys.exit(1)
47
+
48
+ os.environ.update({key: user_env[key] for key in required_keys})
49
+ logger.info("WatsonX credentials validated successfully.")
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))
77
+
78
+ @evaluation_app.command(name="evaluate", help="Evaluate an agent against a set of test cases")
79
+ def evaluate(
80
+ config_file: Annotated[
81
+ Optional[str],
82
+ typer.Option(
83
+ "--config", "-c",
84
+ help="Path to YAML configuration file containing evaluation settings."
85
+ )
86
+ ] = None,
87
+ test_paths: Annotated[
88
+ Optional[str],
89
+ typer.Option(
90
+ "--test-paths", "-p",
91
+ help="Paths to the test files and/or directories to evaluate, separated by commas."
92
+ ),
93
+ ] = None,
94
+ output_dir: Annotated[
95
+ Optional[str],
96
+ typer.Option(
97
+ "--output-dir", "-o",
98
+ help="Directory to save the evaluation results."
99
+ )
100
+ ] = None,
101
+ user_env_file: Annotated[
102
+ Optional[str],
103
+ typer.Option(
104
+ "--env-file", "-e",
105
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
106
+ ),
107
+ ] = None
108
+ ):
109
+ if not config_file:
110
+ if not test_paths or not output_dir:
111
+ logger.error("Error: Both --test-paths and --output-dir must be provided when not using a config file")
112
+ exit(1)
113
+
114
+ validate_watsonx_credentials(user_env_file)
115
+ controller = EvaluationsController()
116
+ controller.evaluate(config_file=config_file, test_paths=test_paths, output_dir=output_dir)
117
+
118
+
119
+ @evaluation_app.command(name="record", help="Record chat sessions and create test cases")
120
+ def record(
121
+ output_dir: Annotated[
122
+ Optional[str],
123
+ typer.Option(
124
+ "--output-dir", "-o",
125
+ help="Directory to save the recorded chats."
126
+ )
127
+ ] = None,
128
+ user_env_file: Annotated[
129
+ Optional[str],
130
+ typer.Option(
131
+ "--env-file", "-e",
132
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
133
+ ),
134
+ ] = None
135
+ ):
136
+ validate_watsonx_credentials(user_env_file)
137
+ controller = EvaluationsController()
138
+ controller.record(output_dir=output_dir)
139
+
140
+
141
+ @evaluation_app.command(name="generate", help="Generate test cases from user stories and tools")
142
+ def generate(
143
+ stories_path: Annotated[
144
+ str,
145
+ typer.Option(
146
+ "--stories-path", "-s",
147
+ help="Path to the CSV file containing user stories for test case generation. "
148
+ "The file has 'story' and 'agent' columns."
149
+ )
150
+ ],
151
+ tools_path: Annotated[
152
+ str,
153
+ typer.Option(
154
+ "--tools-path", "-t",
155
+ help="Path to the directory containing tool definitions."
156
+ )
157
+ ],
158
+ output_dir: Annotated[
159
+ Optional[str],
160
+ typer.Option(
161
+ "--output-dir", "-o",
162
+ help="Directory to save the generated test cases."
163
+ )
164
+ ] = None,
165
+ user_env_file: Annotated[
166
+ Optional[str],
167
+ typer.Option(
168
+ "--env-file", "-e",
169
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
170
+ ),
171
+ ] = None
172
+ ):
173
+ validate_watsonx_credentials(user_env_file)
174
+ controller = EvaluationsController()
175
+ controller.generate(stories_path=stories_path, tools_path=tools_path, output_dir=output_dir)
176
+
177
+
178
+ @evaluation_app.command(name="analyze", help="Analyze the results of an evaluation run")
179
+ def analyze(data_path: Annotated[
180
+ str,
181
+ typer.Option(
182
+ "--data-path", "-d",
183
+ help="Path to the directory that has the saved results"
184
+ )
185
+ ],
186
+ user_env_file: Annotated[
187
+ Optional[str],
188
+ typer.Option(
189
+ "--env-file", "-e",
190
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
191
+ ),
192
+ ] = None):
193
+
194
+ validate_watsonx_credentials(user_env_file)
195
+ controller = EvaluationsController()
196
+ controller.analyze(data_path=data_path)
197
+
198
+ @evaluation_app.command(name="validate-external", help="Validate an external agent against a set of inputs")
199
+ def validate_external(
200
+ data_path: Annotated[
201
+ str,
202
+ typer.Option(
203
+ "--tsv", "-t",
204
+ help="Path to .tsv file of inputs"
205
+ )
206
+ ],
207
+ external_agent_config: Annotated[
208
+ str,
209
+ typer.Option(
210
+ "--external-agent-config", "-ext",
211
+ help="Path to the external agent yaml",
212
+
213
+ )
214
+ ],
215
+ credential: Annotated[
216
+ str,
217
+ typer.Option(
218
+ "--credential", "-crd",
219
+ help="credential string",
220
+ rich_help_panel="Parameters for Validation"
221
+ )
222
+ ] = None,
223
+ output_dir: Annotated[
224
+ str,
225
+ typer.Option(
226
+ "--output", "-o",
227
+ help="where to save the validation results"
228
+ )
229
+ ] = "./test_external_agent",
230
+ user_env_file: Annotated[
231
+ Optional[str],
232
+ typer.Option(
233
+ "--env-file", "-e",
234
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
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
+ )
245
+ ] = None
246
+ ):
247
+
248
+ validate_watsonx_credentials(user_env_file)
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))
@@ -0,0 +1,171 @@
1
+ import logging
2
+ import os.path
3
+ from typing import List, Dict, Optional, Tuple
4
+ import csv
5
+ from pathlib import Path
6
+ import rich
7
+ from wxo_agentic_evaluation import main as evaluate
8
+ from wxo_agentic_evaluation.tool_planner import build_snapshot
9
+ from wxo_agentic_evaluation.analyze_run import analyze
10
+ from wxo_agentic_evaluation.batch_annotate import generate_test_cases_from_stories
11
+ from wxo_agentic_evaluation.arg_configs import TestConfig, AuthConfig, LLMUserConfig, ChatRecordingConfig, AnalyzeConfig
12
+ from wxo_agentic_evaluation.record_chat import record_chats
13
+ from wxo_agentic_evaluation.external_agent.external_validate import ExternalAgentValidation
14
+ from wxo_agentic_evaluation.external_agent.performance_test import ExternalAgentPerformanceTest
15
+ from ibm_watsonx_orchestrate import __version__
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
17
+ from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
18
+ from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import AgentsController
19
+ from ibm_watsonx_orchestrate.agent_builder.agents import AgentKind
20
+ import uuid
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class EvaluationsController:
26
+ def __init__(self):
27
+ pass
28
+
29
+ def _get_env_config(self) -> tuple[str, str, str | None]:
30
+ cfg = Config()
31
+ auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
32
+
33
+ url = cfg.get_active_env_config(ENV_WXO_URL_OPT)
34
+ tenant_name = cfg.get_active_env()
35
+
36
+ existing_auth_config = auth_cfg.get(AUTH_SECTION_HEADER).get(tenant_name, {})
37
+ token = existing_auth_config.get(AUTH_MCSP_TOKEN_OPT) if existing_auth_config else None
38
+
39
+ return url, tenant_name, token
40
+
41
+ def evaluate(self, config_file: Optional[str] = None, test_paths: Optional[str] = None, output_dir: Optional[str] = None) -> None:
42
+ url, tenant_name, token = self._get_env_config()
43
+
44
+ config_data = {
45
+ "wxo_lite_version": __version__,
46
+ "auth_config": AuthConfig(
47
+ url=url,
48
+ tenant_name=tenant_name,
49
+ token=token
50
+ )
51
+ }
52
+
53
+ if config_file:
54
+ logger.info(f"Loading configuration from {config_file}")
55
+ with open(config_file, 'r') as f:
56
+ file_config = yaml_safe_load(f) or {}
57
+
58
+ if "auth_config" in file_config:
59
+ auth_config_data = file_config.pop("auth_config")
60
+ config_data["auth_config"] = AuthConfig(**auth_config_data)
61
+
62
+ if "llm_user_config" in file_config:
63
+ llm_config_data = file_config.pop("llm_user_config")
64
+ config_data["llm_user_config"] = LLMUserConfig(**llm_config_data)
65
+
66
+ config_data.update(file_config)
67
+
68
+ if test_paths:
69
+ config_data["test_paths"] = test_paths.split(",")
70
+ logger.info(f"Using test paths: {config_data['test_paths']}")
71
+ if output_dir:
72
+ config_data["output_dir"] = output_dir
73
+ logger.info(f"Using output directory: {config_data['output_dir']}")
74
+
75
+ config = TestConfig(**config_data)
76
+
77
+ evaluate.main(config)
78
+
79
+ def record(self, output_dir) -> None:
80
+
81
+
82
+ random_uuid = str(uuid.uuid4())
83
+
84
+ url, tenant_name, token = self._get_env_config()
85
+ config_data = {
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)),
87
+ "service_url": url,
88
+ "tenant_name": tenant_name,
89
+ "token": token
90
+ }
91
+
92
+ config_data["output_dir"].mkdir(parents=True, exist_ok=True)
93
+ logger.info(f"Recording chat sessions to {config_data['output_dir']}")
94
+
95
+ record_chats(ChatRecordingConfig(**config_data))
96
+
97
+ def generate(self, stories_path: str, tools_path: str, output_dir: str) -> None:
98
+ stories_path = Path(stories_path)
99
+ tools_path = Path(tools_path)
100
+
101
+ if output_dir is None:
102
+ output_dir = stories_path.parent
103
+ else:
104
+ output_dir = Path(output_dir)
105
+
106
+ output_dir.mkdir(parents=True, exist_ok=True)
107
+
108
+ stories_by_agent = {}
109
+ with stories_path.open("r", encoding="utf-8", newline='') as f:
110
+ csv_reader = csv.DictReader(f)
111
+ for row in csv_reader:
112
+ agent_name = row["agent"]
113
+ if agent_name not in stories_by_agent:
114
+ stories_by_agent[agent_name] = []
115
+ stories_by_agent[agent_name].append(row["story"])
116
+
117
+ for agent_name, stories in stories_by_agent.items():
118
+ logger.info(f"Found {len(stories)} stories for agent '{agent_name}'")
119
+ try:
120
+ agent_controller = AgentsController()
121
+ agent = agent_controller.get_agent(agent_name, AgentKind.NATIVE)
122
+ allowed_tools = agent_controller.get_agent_tool_names(agent.tools)
123
+ except Exception as e:
124
+ logger.warning(f"Could not get tools for agent {agent_name}: {str(e)}")
125
+ allowed_tools = []
126
+
127
+
128
+ logger.info(f"Running tool planner for agent {agent_name}")
129
+ agent_snapshot_path = output_dir / f"{agent_name}_snapshot_llm.json"
130
+ build_snapshot(agent_name, tools_path, stories, agent_snapshot_path)
131
+
132
+ logger.info(f"Running batch annotate for agent {agent_name}")
133
+ generate_test_cases_from_stories(
134
+ agent_name=agent_name,
135
+ stories=stories,
136
+ tools_path=tools_path,
137
+ snapshot_path=agent_snapshot_path,
138
+ output_dir=output_dir / f"{agent_name}_test_cases",
139
+ allowed_tools=allowed_tools,
140
+ num_variants=2
141
+ )
142
+
143
+ logger.info("Test cases stored at: %s", output_dir)
144
+
145
+ def analyze(self, data_path: str) -> None:
146
+ config = AnalyzeConfig(data_path=data_path)
147
+ analyze(config)
148
+
149
+ def summarize(self) -> None:
150
+ pass
151
+
152
+ def external_validate(self, config: Dict, data: List[str], credential:str, add_context: bool = False):
153
+ validator = ExternalAgentValidation(credential=credential,
154
+ auth_scheme=config["auth_scheme"],
155
+ service_url=config["api_url"])
156
+
157
+ summary = []
158
+ for entry in data:
159
+ results = validator.call_validation(entry, add_context)
160
+ summary.append(results)
161
+
162
+ return summary
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
@@ -50,7 +50,7 @@ def list_knowledge_bases(
50
50
  controller = KnowledgeBaseController()
51
51
  controller.list_knowledge_bases(verbose=verbose)
52
52
 
53
- @knowledge_bases_app.command(name="remove", help="Delete a knowlege base and all ingested documents")
53
+ @knowledge_bases_app.command(name="remove", help="Remove a knowledge base. Note that if your knowledge base was created by uploading documents (for built-in Milvus), the ingested information from your documents will also be deleted. If your knowledge base uses an external knowledge source through an index_config definition, your index will not be deleted.")
54
54
  def remove_knowledge_base(
55
55
  name: Annotated[
56
56
  str,
@@ -64,7 +64,7 @@ def remove_knowledge_base(
64
64
  controller = KnowledgeBaseController()
65
65
  controller.remove_knowledge_base(id=id, name=name)
66
66
 
67
- @knowledge_bases_app.command(name="status", help="Get the status of a knowlege base")
67
+ @knowledge_bases_app.command(name="status", help="Get the status of a knowledge base")
68
68
  def knowledge_base_status(
69
69
  name: Annotated[
70
70
  str,
@@ -197,10 +197,10 @@ class KnowledgeBaseController:
197
197
  )
198
198
 
199
199
  column_args = {
200
- "Name": {},
200
+ "Name": {"overflow": "fold"},
201
201
  "Description": {},
202
202
  "App ID": {},
203
- "ID": {}
203
+ "ID": {"overflow": "fold"}
204
204
  }
205
205
 
206
206
  for column in column_args:
@@ -23,28 +23,34 @@ PROVIDER_EXTRA_PROPERTIES_LUT = {
23
23
  # 'azure_ad_token',
24
24
  # 'azure_model_name'
25
25
  # },
26
- # ModelProvider.BEDROCK: {
27
- # 'aws_secret_access_key',
28
- # 'aws_access_key_id',
29
- # 'aws_session_token',
30
- # 'aws_region',
31
- # 'aws_auth_type',
32
- # 'aws_role_arn',
33
- # 'aws_external_id',
34
- # 'aws_s3_bucket',
35
- # 'aws_s3_object_key',
36
- # 'aws_bedrock_model',
37
- # 'aws_server_side_encryption',
38
- # 'aws_server_side_encryption_kms_key_id'
39
- # },
40
- # ModelProvider.VERTEX_AI: {
41
- # 'vertex_region',
42
- # 'vertex_project_id',
43
- # 'vertex_service_account_json',
44
- # 'vertex_storage_bucket_name',
45
- # 'vertex_model_name',
46
- # 'filename'
47
- # },
26
+ ModelProvider.AZURE_OPENAI: {
27
+ 'azure_resource_name',
28
+ 'azure_deployment_id',
29
+ 'azure_api_version',
30
+ 'azure_model_name'
31
+ },
32
+ ModelProvider.BEDROCK: {
33
+ 'aws_secret_access_key',
34
+ 'aws_access_key_id',
35
+ 'aws_session_token',
36
+ 'aws_region',
37
+ 'aws_auth_type',
38
+ 'aws_role_arn',
39
+ 'aws_external_id',
40
+ 'aws_s3_bucket',
41
+ 'aws_s3_object_key',
42
+ 'aws_bedrock_model',
43
+ 'aws_server_side_encryption',
44
+ 'aws_server_side_encryption_kms_key_id'
45
+ },
46
+ ModelProvider.VERTEX_AI: {
47
+ 'vertex_region',
48
+ 'vertex_project_id',
49
+ 'vertex_service_account_json',
50
+ 'vertex_storage_bucket_name',
51
+ 'vertex_model_name',
52
+ 'filename'
53
+ },
48
54
  # ModelProvider.HUGGINGFACE: {'huggingfaceBaseUrl'},
49
55
  ModelProvider.MISTRAL_AI: {'mistral_fim_completion'},
50
56
  # ModelProvider.STABILITY_AI: {'stability_client_id', 'stability_client_user_id', 'stability_client_version'},
@@ -93,7 +99,8 @@ PROVIDER_REQUIRED_FIELDS = {k:['api_key'] for k in ModelProvider}
93
99
  # Use sets to denote when a requirement is 'or'
94
100
  PROVIDER_REQUIRED_FIELDS.update({
95
101
  ModelProvider.WATSONX: PROVIDER_REQUIRED_FIELDS[ModelProvider.WATSONX] + [{'watsonx_space_id', 'watsonx_project_id', 'watsonx_deployment_id'}],
96
- ModelProvider.OLLAMA: PROVIDER_REQUIRED_FIELDS[ModelProvider.OLLAMA] + ['custom_host']
102
+ ModelProvider.OLLAMA: PROVIDER_REQUIRED_FIELDS[ModelProvider.OLLAMA] + ['custom_host'],
103
+ ModelProvider.BEDROCK: [],
97
104
  })
98
105
 
99
106
  # def env_file_to_model_ProviderConfig(model_name: str, env_file_path: str) -> ProviderConfig | None:
@@ -163,7 +170,7 @@ def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id:
163
170
  if not app_id:
164
171
  missing_credentials_string = f"Missing configuration variable(s) required for the provider {provider}:"
165
172
  else:
166
- missing_credentials_string = f"The following configuration variable(s) for the provider {provider} are not in the spec provider_config:"
173
+ missing_credentials_string = f"Be sure to include the following required fields for provider '{provider}' in the connection '{app_id}':"
167
174
  for cred in missing_credentials:
168
175
  if isinstance(cred, set):
169
176
  cred_str = ' or '.join(list(cred))
@@ -177,7 +184,6 @@ def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id:
177
184
  sys.exit(1)
178
185
  else:
179
186
  logger.info(missing_credentials_string)
180
- logger.info(f"Please ensure these values are set in the connection '{app_id}'.")
181
187
 
182
188
 
183
189
  def validate_ProviderConfig(cfg: ProviderConfig, app_id: str)-> None:
@@ -146,18 +146,18 @@ def models_policy_add(
146
146
  ModelPolicyStrategyMode,
147
147
  typer.Option('--strategy', '-s', help='How to spread traffic across models'),
148
148
  ],
149
+ retry_attempts: Annotated[
150
+ int,
151
+ typer.Option('--retry-attempts', help='The number of attempts to retry'),
152
+ ],
149
153
  strategy_on_code: Annotated[
150
154
  List[int],
151
155
  typer.Option('--strategy-on-code', help='The http status to consider invoking the strategy'),
152
- ],
156
+ ] = None,
153
157
  retry_on_code: Annotated[
154
158
  List[int],
155
159
  typer.Option('--retry-on-code', help='The http status to consider retrying the llm call'),
156
- ],
157
- retry_attempts: Annotated[
158
- int,
159
- typer.Option('--retry-attempts', help='The number of attempts to retry'),
160
- ],
160
+ ] = None,
161
161
  display_name: Annotated[
162
162
  str,
163
163
  typer.Option('--display-name', help='What name should this llm appear as within the ui'),