ibm-watsonx-orchestrate 1.12.2__py3-none-any.whl → 1.13.0b1__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/connections/types.py +34 -3
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +13 -2
- ibm_watsonx_orchestrate/agent_builder/models/types.py +17 -1
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +31 -53
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +54 -28
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +36 -2
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +270 -26
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +30 -3
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_environment_manager.py +158 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +26 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +150 -34
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +50 -18
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +43 -29
- ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
- ibm_watsonx_orchestrate/cli/common.py +26 -0
- ibm_watsonx_orchestrate/cli/config.py +30 -1
- ibm_watsonx_orchestrate/client/agents/agent_client.py +1 -1
- ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +55 -11
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
- ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
- ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +4 -2
- ibm_watsonx_orchestrate/client/utils.py +29 -7
- ibm_watsonx_orchestrate/docker/compose-lite.yml +3 -2
- ibm_watsonx_orchestrate/docker/default.env +15 -10
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +28 -12
- ibm_watsonx_orchestrate/flow_builder/types.py +25 -0
- ibm_watsonx_orchestrate/flow_builder/utils.py +1 -9
- ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
- ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
- ibm_watsonx_orchestrate/utils/environment.py +165 -20
- ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
- ibm_watsonx_orchestrate/utils/tokens.py +51 -0
- ibm_watsonx_orchestrate/utils/utils.py +57 -2
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/RECORD +53 -48
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.12.2.dist-info → ibm_watsonx_orchestrate-1.13.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,158 @@
|
|
1
|
+
import logging
|
2
|
+
import yaml
|
3
|
+
from typing import Mapping, Any
|
4
|
+
from enum import StrEnum
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
from ibm_watsonx_orchestrate.cli.commands.agents.agents_controller import (
|
8
|
+
AgentsController,
|
9
|
+
Agent,
|
10
|
+
ExternalAgent,
|
11
|
+
AssistantAgent,
|
12
|
+
)
|
13
|
+
from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import (
|
14
|
+
ToolsController,
|
15
|
+
BaseTool,
|
16
|
+
)
|
17
|
+
from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_controller import (
|
18
|
+
KnowledgeBaseController,
|
19
|
+
KnowledgeBase,
|
20
|
+
)
|
21
|
+
from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_controller import (
|
22
|
+
parse_file as kb_parse_file,
|
23
|
+
)
|
24
|
+
from ibm_watsonx_orchestrate.cli.commands.evaluations.evaluations_controller import (
|
25
|
+
EvaluationsController,
|
26
|
+
EvaluateMode,
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
|
32
|
+
|
33
|
+
class ArtifactTypes(StrEnum):
|
34
|
+
"""The allowed artifacts in the environment manager path.
|
35
|
+
|
36
|
+
The environment manager config looks like this:
|
37
|
+
```json
|
38
|
+
env1:
|
39
|
+
agent:
|
40
|
+
agents_path: None
|
41
|
+
tools:
|
42
|
+
tools_path: None
|
43
|
+
tool_kind: None
|
44
|
+
# any other tool flags
|
45
|
+
knowledge:
|
46
|
+
knowledge_base_path: None
|
47
|
+
test_config: # path to config.yaml
|
48
|
+
clean_up: True
|
49
|
+
```
|
50
|
+
The allowed artifacts/keys are "agent", "tools", "knowledge"
|
51
|
+
"""
|
52
|
+
|
53
|
+
agent = "agent"
|
54
|
+
tools = "tools"
|
55
|
+
knowledge = "knowledge"
|
56
|
+
|
57
|
+
|
58
|
+
class TestCaseManager:
|
59
|
+
def __init__(
|
60
|
+
self,
|
61
|
+
env_settings: Mapping[str, Any],
|
62
|
+
output_dir: str,
|
63
|
+
mode: EvaluateMode = EvaluateMode.default,
|
64
|
+
):
|
65
|
+
self.env_settings = env_settings
|
66
|
+
self.cleanup = env_settings.get("clean_up", False)
|
67
|
+
self.output_dir = output_dir
|
68
|
+
self.mode = mode
|
69
|
+
|
70
|
+
self.agent_controller = AgentsController()
|
71
|
+
self.knowledge_controller = KnowledgeBaseController()
|
72
|
+
self.tool_controller = None
|
73
|
+
if (tool_settings := env_settings.get(ArtifactTypes.tools)):
|
74
|
+
self.tool_controller = ToolsController(
|
75
|
+
tool_kind=tool_settings.get("kind"),
|
76
|
+
file=tool_settings.get("file"),
|
77
|
+
requirements_file=tool_settings.get("requirements_file")
|
78
|
+
)
|
79
|
+
|
80
|
+
self.imported_artifacts = []
|
81
|
+
|
82
|
+
def __enter__(self):
|
83
|
+
for artifact in [
|
84
|
+
ArtifactTypes.tools,
|
85
|
+
ArtifactTypes.knowledge,
|
86
|
+
ArtifactTypes.agent,
|
87
|
+
]:
|
88
|
+
if artifact not in self.env_settings:
|
89
|
+
continue
|
90
|
+
|
91
|
+
artifact_settings = self.env_settings.get(artifact)
|
92
|
+
if artifact == ArtifactTypes.tools:
|
93
|
+
tools = ToolsController.import_tool(**artifact_settings)
|
94
|
+
# import_tool returns Iterator[BaseTool], copy the iterator into a list for preservation
|
95
|
+
# this is needed if user wants environment cleanup
|
96
|
+
tools = [tool for tool in tools]
|
97
|
+
self.imported_artifacts.append(tools)
|
98
|
+
self.tool_controller.publish_or_update_tools(tools)
|
99
|
+
elif artifact == ArtifactTypes.knowledge:
|
100
|
+
KnowledgeBaseController.import_knowledge_base(**artifact_settings)
|
101
|
+
kb_spec = kb_parse_file(artifact_settings.get("file"))
|
102
|
+
self.imported_artifacts.append(kb_spec)
|
103
|
+
elif artifact == ArtifactTypes.agent:
|
104
|
+
artifact_settings["app_id"] = artifact_settings.get("app_id", None)
|
105
|
+
agents = AgentsController.import_agent(**artifact_settings)
|
106
|
+
self.agent_controller.publish_or_update_agents(agents)
|
107
|
+
self.imported_artifacts.append(agents)
|
108
|
+
|
109
|
+
eval = EvaluationsController()
|
110
|
+
eval.evaluate(
|
111
|
+
test_paths=self.env_settings.get("test_paths"),
|
112
|
+
output_dir=self.output_dir,
|
113
|
+
mode=self.mode,
|
114
|
+
)
|
115
|
+
|
116
|
+
return self
|
117
|
+
|
118
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
119
|
+
if self.cleanup:
|
120
|
+
logger.info("Cleaning environment")
|
121
|
+
for artifact in self.imported_artifacts:
|
122
|
+
# artifact can be a list of agents, tools
|
123
|
+
for item in artifact:
|
124
|
+
if isinstance(item, BaseTool):
|
125
|
+
self.tool_controller.remove_tool(item.__tool_spec__.name)
|
126
|
+
if isinstance(item, KnowledgeBase):
|
127
|
+
self.knowledge_controller.remove_knowledge_base(
|
128
|
+
item.id, item.name
|
129
|
+
)
|
130
|
+
if isinstance(item, (Agent, AssistantAgent, ExternalAgent)):
|
131
|
+
self.agent_controller.remove_agent(item.name, item.kind)
|
132
|
+
|
133
|
+
|
134
|
+
def run_environment_manager(
|
135
|
+
environment_manager_path: str,
|
136
|
+
mode: EvaluateMode = EvaluateMode.default,
|
137
|
+
output_dir: str = None,
|
138
|
+
):
|
139
|
+
with open(environment_manager_path, encoding="utf-8", mode="r") as f:
|
140
|
+
env_settings = yaml.load(f, Loader=yaml.SafeLoader)
|
141
|
+
|
142
|
+
for env in env_settings:
|
143
|
+
if not env_settings.get(env).get("enabled"):
|
144
|
+
continue
|
145
|
+
results_folder = Path(output_dir) / env
|
146
|
+
results_folder.mkdir(parents=True, exist_ok=True)
|
147
|
+
logger.info(
|
148
|
+
"Processing environment: '%s'. Results will be saved to '%s'",
|
149
|
+
env,
|
150
|
+
results_folder,
|
151
|
+
)
|
152
|
+
|
153
|
+
with TestCaseManager(
|
154
|
+
env_settings=env_settings.get(env),
|
155
|
+
output_dir=str(results_folder),
|
156
|
+
mode=mode,
|
157
|
+
):
|
158
|
+
logger.info("Finished evaluation for environment: '%s'", env)
|
@@ -59,3 +59,29 @@ def knowledge_base_status(
|
|
59
59
|
):
|
60
60
|
controller = KnowledgeBaseController()
|
61
61
|
controller.knowledge_base_status(id=id, name=name)
|
62
|
+
|
63
|
+
@knowledge_bases_app.command(name="export", help='Export a knowledge base spec to a yaml')
|
64
|
+
def knowledge_base_export(
|
65
|
+
output_file: Annotated[
|
66
|
+
str,
|
67
|
+
typer.Option(
|
68
|
+
"--output",
|
69
|
+
"-o",
|
70
|
+
help="Path to a where the zip file containing the exported data should be saved",
|
71
|
+
),
|
72
|
+
],
|
73
|
+
name: Annotated[
|
74
|
+
str,
|
75
|
+
typer.Option("--name", "-n", help="The name of the knowledge base you want to export"),
|
76
|
+
]=None,
|
77
|
+
id: Annotated[
|
78
|
+
str,
|
79
|
+
typer.Option("--id", "-i", help="The ID of the knowledge base you wish export"),
|
80
|
+
]=None,
|
81
|
+
):
|
82
|
+
controller = KnowledgeBaseController()
|
83
|
+
controller.knowledge_base_export(
|
84
|
+
id=id,
|
85
|
+
name=name,
|
86
|
+
output_path=output_file
|
87
|
+
)
|
@@ -5,15 +5,20 @@ import requests
|
|
5
5
|
import logging
|
6
6
|
import importlib
|
7
7
|
import inspect
|
8
|
+
import yaml
|
8
9
|
from pathlib import Path
|
9
|
-
from typing import List
|
10
|
+
from typing import List, Any, Optional
|
11
|
+
from zipfile import ZipFile
|
12
|
+
from io import BytesIO
|
10
13
|
|
11
14
|
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import KnowledgeBase
|
12
15
|
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
|
13
16
|
from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
|
14
17
|
from ibm_watsonx_orchestrate.client.connections import get_connections_client
|
15
18
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
16
|
-
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload
|
19
|
+
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload, KnowledgeBaseListEntry
|
20
|
+
from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
|
21
|
+
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import KnowledgeBaseKind, IndexConnection, SpecVersion
|
17
22
|
|
18
23
|
logger = logging.getLogger(__name__)
|
19
24
|
|
@@ -63,6 +68,32 @@ def build_file_object(file_dir: str, file: str | FileUpload):
|
|
63
68
|
return ('files', (get_file_name(file.path), open(get_relative_file_path(file.path, file_dir), 'rb')))
|
64
69
|
return ('files', (get_file_name(file), open(get_relative_file_path(file, file_dir), 'rb')))
|
65
70
|
|
71
|
+
def build_connections_map(key_attr: str) -> dict:
|
72
|
+
connections_client = get_connections_client()
|
73
|
+
connections = connections_client.list()
|
74
|
+
|
75
|
+
return {getattr(conn, key_attr): conn for conn in connections}
|
76
|
+
|
77
|
+
def get_index_config(kb: KnowledgeBase, index: int = 0) -> IndexConnection | None:
|
78
|
+
if kb.conversational_search_tool is not None \
|
79
|
+
and kb.conversational_search_tool.index_config is not None \
|
80
|
+
and len(kb.conversational_search_tool.index_config) > index:
|
81
|
+
|
82
|
+
return kb.conversational_search_tool.index_config[index]
|
83
|
+
return None
|
84
|
+
|
85
|
+
def get_kb_app_id(kb: KnowledgeBase) -> str | None:
|
86
|
+
index_config = get_index_config(kb)
|
87
|
+
if not index_config:
|
88
|
+
return
|
89
|
+
return index_config.app_id
|
90
|
+
|
91
|
+
def get_kb_connection_id(kb: KnowledgeBase) -> str | None:
|
92
|
+
index_config = get_index_config(kb)
|
93
|
+
if not index_config:
|
94
|
+
return
|
95
|
+
return index_config.connection_id
|
96
|
+
|
66
97
|
class KnowledgeBaseController:
|
67
98
|
def __init__(self):
|
68
99
|
self.client = None
|
@@ -78,24 +109,23 @@ class KnowledgeBaseController:
|
|
78
109
|
|
79
110
|
knowledge_bases = parse_file(file=file)
|
80
111
|
|
81
|
-
|
82
|
-
connections_client = get_connections_client()
|
83
|
-
connection_id = None
|
84
|
-
|
85
|
-
connections = connections_client.get_draft_by_app_id(app_id=app_id)
|
86
|
-
if not connections:
|
87
|
-
logger.error(f"No connection exists with the app-id '{app_id}'")
|
88
|
-
exit(1)
|
89
|
-
|
90
|
-
connection_id = connections.connection_id
|
91
|
-
|
92
|
-
for kb in knowledge_bases:
|
93
|
-
if kb.conversational_search_tool and kb.conversational_search_tool.index_config and len(kb.conversational_search_tool.index_config) > 0:
|
94
|
-
kb.conversational_search_tool.index_config[0].connection_id = connection_id
|
112
|
+
connections_map = None
|
95
113
|
|
96
114
|
existing_knowledge_bases = client.get_by_names([kb.name for kb in knowledge_bases])
|
97
115
|
|
98
116
|
for kb in knowledge_bases:
|
117
|
+
app_id = app_id if app_id else get_kb_app_id(kb)
|
118
|
+
if app_id:
|
119
|
+
if not connections_map:
|
120
|
+
connections_map = build_connections_map("app_id")
|
121
|
+
conn = connections_map.get(app_id)
|
122
|
+
if conn:
|
123
|
+
index_config = get_index_config(kb)
|
124
|
+
if index_config:
|
125
|
+
index_config.connection_id = conn.connection_id
|
126
|
+
else:
|
127
|
+
logger.error(f"No connection exists with the app-id '{app_id}'")
|
128
|
+
exit(1)
|
99
129
|
try:
|
100
130
|
file_dir = "/".join(file.split("/")[:-1])
|
101
131
|
|
@@ -198,8 +228,7 @@ class KnowledgeBaseController:
|
|
198
228
|
|
199
229
|
logger.info(f"Knowledge base '{kb.name}' updated successfully")
|
200
230
|
|
201
|
-
|
202
|
-
def knowledge_base_status( self, id: str, name: str) -> None:
|
231
|
+
def knowledge_base_status( self, id: str, name: str, format: ListFormats = None) -> dict | str | None:
|
203
232
|
knowledge_base_id = self.get_id(id, name)
|
204
233
|
response = self.get_client().status(knowledge_base_id)
|
205
234
|
|
@@ -219,13 +248,25 @@ class KnowledgeBaseController:
|
|
219
248
|
|
220
249
|
response["id"] = kbID
|
221
250
|
|
251
|
+
if format == ListFormats.JSON:
|
252
|
+
return response
|
253
|
+
|
254
|
+
|
222
255
|
[table.add_column(to_column_name(col), {}) for col in response.keys()]
|
223
256
|
table.add_row(*[str(val) for val in response.values()])
|
224
257
|
|
258
|
+
if format == ListFormats.Table:
|
259
|
+
return rich_table_to_markdown(table)
|
260
|
+
|
225
261
|
rich.print(table)
|
226
262
|
|
227
263
|
|
228
|
-
def list_knowledge_bases(self, verbose: bool=False):
|
264
|
+
def list_knowledge_bases(self, verbose: bool=False, format: ListFormats=None)-> List[dict[str, Any]] | List[KnowledgeBaseListEntry] | str | None:
|
265
|
+
|
266
|
+
if verbose and format:
|
267
|
+
logger.error("For knowledge base list, `--verbose` and `--format` are mutually exclusive options")
|
268
|
+
sys.exit(1)
|
269
|
+
|
229
270
|
response = self.get_client().get()
|
230
271
|
knowledge_bases = [KnowledgeBase.model_validate(knowledge_base) for knowledge_base in response]
|
231
272
|
|
@@ -234,7 +275,9 @@ class KnowledgeBaseController:
|
|
234
275
|
for kb in knowledge_bases:
|
235
276
|
knowledge_base_list.append(json.loads(kb.model_dump_json(exclude_none=True)))
|
236
277
|
rich.print(rich.json.JSON(json.dumps(knowledge_base_list, indent=4)))
|
278
|
+
return knowledge_base_list
|
237
279
|
else:
|
280
|
+
knowledge_base_details=[]
|
238
281
|
table = rich.table.Table(
|
239
282
|
show_header=True,
|
240
283
|
header_style="bold white",
|
@@ -251,25 +294,34 @@ class KnowledgeBaseController:
|
|
251
294
|
for column in column_args:
|
252
295
|
table.add_column(column, **column_args[column])
|
253
296
|
|
297
|
+
connections_dict = build_connections_map("connection_id")
|
298
|
+
|
254
299
|
for kb in knowledge_bases:
|
255
300
|
app_id = ""
|
256
|
-
|
257
|
-
if
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
kb.
|
266
|
-
|
267
|
-
app_id,
|
268
|
-
str(kb.id)
|
301
|
+
connection_id = get_kb_connection_id(kb)
|
302
|
+
if connection_id is not None:
|
303
|
+
conn = connections_dict.get(connection_id)
|
304
|
+
if conn:
|
305
|
+
app_id = conn.app_id
|
306
|
+
|
307
|
+
entry = KnowledgeBaseListEntry(
|
308
|
+
name=kb.name,
|
309
|
+
id=str(kb.id),
|
310
|
+
description=kb.description,
|
311
|
+
app_id=app_id
|
269
312
|
)
|
313
|
+
if format == ListFormats.JSON:
|
314
|
+
knowledge_base_details.append(entry)
|
315
|
+
else:
|
316
|
+
table.add_row(*entry.get_row_details())
|
270
317
|
|
271
|
-
|
272
|
-
|
318
|
+
match format:
|
319
|
+
case ListFormats.JSON:
|
320
|
+
return knowledge_base_details
|
321
|
+
case ListFormats.Table:
|
322
|
+
return rich_table_to_markdown(table)
|
323
|
+
case _:
|
324
|
+
rich.print(table)
|
273
325
|
|
274
326
|
def remove_knowledge_base(self, id: str, name: str):
|
275
327
|
knowledge_base_id = self.get_id(id, name)
|
@@ -283,4 +335,68 @@ class KnowledgeBaseController:
|
|
283
335
|
logger.warning(f"No knowledge base {logEnding} found")
|
284
336
|
logger.error(e.response.text)
|
285
337
|
exit(1)
|
338
|
+
|
339
|
+
def get_knowledge_base(self, id) -> KnowledgeBase:
|
340
|
+
client = self.get_client()
|
341
|
+
try:
|
342
|
+
return KnowledgeBase.model_validate(client.get_by_id(id))
|
343
|
+
except requests.HTTPError as e:
|
344
|
+
if e.response.status_code == 404:
|
345
|
+
logger.error(f"No knowledge base {id} found")
|
346
|
+
else:
|
347
|
+
logger.error(e.response.text)
|
348
|
+
exit(1)
|
349
|
+
|
350
|
+
|
351
|
+
def knowledge_base_export(self,
|
352
|
+
output_path: str,
|
353
|
+
id: Optional[str] = None,
|
354
|
+
name: Optional[str] = None,
|
355
|
+
zip_file_out: Optional[ZipFile] = None) -> None:
|
356
|
+
output_file = Path(output_path)
|
357
|
+
output_file_extension = output_file.suffix
|
358
|
+
if output_file_extension not in {".yaml", ".yml"} :
|
359
|
+
logger.error(f"Output file must end with the extension '.yaml'/'.yml'. Provided file '{output_path}' ends with '{output_file_extension}'")
|
360
|
+
sys.exit(1)
|
361
|
+
|
362
|
+
knowledge_base_id = self.get_id(id, name)
|
363
|
+
logEnding = f"with ID '{id}'" if id else f"'{name}'"
|
364
|
+
|
365
|
+
logger.info(f"Exporting spec for knowledge base {logEnding}'")
|
366
|
+
|
367
|
+
knowledge_base = self.get_knowledge_base(knowledge_base_id)
|
286
368
|
|
369
|
+
if not knowledge_base:
|
370
|
+
logger.error(f"Knowledge base'{knowledge_base_id}' not found.'")
|
371
|
+
return
|
372
|
+
|
373
|
+
knowledge_base.tenant_id = None
|
374
|
+
knowledge_base.id = None
|
375
|
+
knowledge_base.spec_version = SpecVersion.V1
|
376
|
+
knowledge_base.kind = KnowledgeBaseKind.KNOWLEDGE_BASE
|
377
|
+
|
378
|
+
connection_id = get_kb_connection_id(knowledge_base)
|
379
|
+
if connection_id:
|
380
|
+
connections_map = build_connections_map("connection_id")
|
381
|
+
conn = connections_map.get(connection_id)
|
382
|
+
if conn:
|
383
|
+
index_config = get_index_config(knowledge_base)
|
384
|
+
index_config.app_id = conn.app_id
|
385
|
+
index_config.connection_id = None
|
386
|
+
else:
|
387
|
+
logger.warning(f"Connection '{connection_id}' not found, unable to resolve app_id for Knowledge base {logEnding}")
|
388
|
+
|
389
|
+
knowledge_base_spec = knowledge_base.model_dump(mode="json", exclude_none=True, exclude_unset=True)
|
390
|
+
if zip_file_out:
|
391
|
+
knowledge_base_spec_yaml = yaml.dump(knowledge_base_spec, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
392
|
+
knowledge_base_spec_yaml_bytes = knowledge_base_spec_yaml.encode("utf-8")
|
393
|
+
knowledge_base_spec_yaml_file = BytesIO(knowledge_base_spec_yaml_bytes)
|
394
|
+
zip_file_out.writestr(
|
395
|
+
output_path,
|
396
|
+
knowledge_base_spec_yaml_file.getvalue()
|
397
|
+
)
|
398
|
+
else:
|
399
|
+
with open(output_path, 'w') as outfile:
|
400
|
+
yaml.dump(knowledge_base_spec, outfile, sort_keys=False, default_flow_style=False, allow_unicode=True)
|
401
|
+
|
402
|
+
logger.info(f"Successfully exported for knowledge base {logEnding} to '{output_path}'")
|
@@ -136,11 +136,11 @@ def models_policy_import(
|
|
136
136
|
def models_policy_add(
|
137
137
|
name: Annotated[
|
138
138
|
str,
|
139
|
-
typer.Option("--name", "-n", help="The name of the model to
|
139
|
+
typer.Option("--name", "-n", help="The name of the model policy to add"),
|
140
140
|
],
|
141
141
|
models: Annotated[
|
142
142
|
List[str],
|
143
|
-
typer.Option('--model', '-m', help='
|
143
|
+
typer.Option('--model', '-m', help='A list of model names the policy should contain'),
|
144
144
|
],
|
145
145
|
strategy: Annotated[
|
146
146
|
ModelPolicyStrategyMode,
|
@@ -6,7 +6,7 @@ import yaml
|
|
6
6
|
import importlib
|
7
7
|
import inspect
|
8
8
|
from pathlib import Path
|
9
|
-
from typing import List
|
9
|
+
from typing import List, Optional
|
10
10
|
|
11
11
|
import requests
|
12
12
|
import rich
|
@@ -16,10 +16,11 @@ from ibm_watsonx_orchestrate.client.model_policies.model_policies_client import
|
|
16
16
|
from ibm_watsonx_orchestrate.agent_builder.model_policies.types import ModelPolicy, ModelPolicyInner, \
|
17
17
|
ModelPolicyRetry, ModelPolicyStrategy, ModelPolicyStrategyMode, ModelPolicyTarget
|
18
18
|
from ibm_watsonx_orchestrate.client.models.models_client import ModelsClient
|
19
|
-
from ibm_watsonx_orchestrate.agent_builder.models.types import VirtualModel, ProviderConfig, ModelType, ANTHROPIC_DEFAULT_MAX_TOKENS
|
19
|
+
from ibm_watsonx_orchestrate.agent_builder.models.types import VirtualModel, ProviderConfig, ModelType, ANTHROPIC_DEFAULT_MAX_TOKENS, ModelListEntry
|
20
20
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_cpd_env
|
21
21
|
from ibm_watsonx_orchestrate.client.connections import get_connection_id, ConnectionType
|
22
22
|
from ibm_watsonx_orchestrate.utils.environment import EnvService
|
23
|
+
from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
|
23
24
|
|
24
25
|
logger = logging.getLogger(__name__)
|
25
26
|
|
@@ -149,7 +150,7 @@ class ModelsController:
|
|
149
150
|
self.model_policies_client = instantiate_client(ModelPoliciesClient)
|
150
151
|
return self.model_policies_client
|
151
152
|
|
152
|
-
def list_models(self, print_raw: bool = False) -> None:
|
153
|
+
def list_models(self, print_raw: bool = False, format: Optional[ListFormats] = None) -> List[ModelListEntry] | str |None:
|
153
154
|
models_client: ModelsClient = self.get_models_client()
|
154
155
|
model_policies_client: ModelPoliciesClient = self.get_model_policies_client()
|
155
156
|
global WATSONX_URL
|
@@ -224,6 +225,7 @@ class ModelsController:
|
|
224
225
|
|
225
226
|
console.print("[yellow]★[/yellow] [italic dim]indicates a supported and preferred model[/italic dim]\n[blue dim]✨️[/blue dim] [italic dim]indicates a model from a custom provider[/italic dim]" )
|
226
227
|
else:
|
228
|
+
model_details = []
|
227
229
|
table = rich.table.Table(
|
228
230
|
show_header=True,
|
229
231
|
title="[bold]Available Models[/bold]",
|
@@ -234,15 +236,32 @@ class ModelsController:
|
|
234
236
|
table.add_column(col)
|
235
237
|
|
236
238
|
for model in (virtual_models + virtual_model_policies):
|
237
|
-
|
239
|
+
entry = ModelListEntry(
|
240
|
+
name=model.name,
|
241
|
+
description=model.description,
|
242
|
+
is_custom=True
|
243
|
+
)
|
244
|
+
model_details.append(entry)
|
245
|
+
table.add_row(*entry.get_row_details())
|
238
246
|
|
239
247
|
for model in sorted_models:
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
248
|
+
name = model.get("model_id", "N/A")
|
249
|
+
entry = ModelListEntry(
|
250
|
+
name=name,
|
251
|
+
description=model.get("short_description"),
|
252
|
+
is_custom=False,
|
253
|
+
recommended=any(pref in name.lower() for pref in preferred_list)
|
254
|
+
)
|
255
|
+
model_details.append(entry)
|
256
|
+
table.add_row(*entry.get_row_details())
|
257
|
+
|
258
|
+
match format:
|
259
|
+
case ListFormats.JSON:
|
260
|
+
return model_details
|
261
|
+
case ListFormats.Table:
|
262
|
+
return rich_table_to_markdown(table)
|
263
|
+
case _:
|
264
|
+
rich.print(table)
|
246
265
|
|
247
266
|
def import_model(self, file: str, app_id: str | None) -> List[VirtualModel]:
|
248
267
|
from ibm_watsonx_orchestrate.cli.commands.models.model_provider_mapper import validate_ProviderConfig # lazily import this because the lut building is expensive
|