ibm-watsonx-orchestrate 1.12.0b0__py3-none-any.whl → 1.13.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 +2 -1
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -5
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +11 -2
- ibm_watsonx_orchestrate/agent_builder/models/types.py +18 -1
- ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +1 -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/base_tool.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +61 -1
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +6 -0
- 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 +29 -53
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -30
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +25 -2
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +249 -14
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +5 -1
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -3
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +3 -2
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +45 -16
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +23 -4
- 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/partners/offering/partners_offering_controller.py +21 -4
- ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +7 -15
- ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +30 -20
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +2 -2
- 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 +79 -36
- 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 +33 -2
- ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +34 -1
- 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/utils.py +29 -7
- ibm_watsonx_orchestrate/docker/compose-lite.yml +58 -8
- ibm_watsonx_orchestrate/docker/default.env +26 -17
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +10 -2
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +90 -16
- ibm_watsonx_orchestrate/flow_builder/node.py +14 -2
- ibm_watsonx_orchestrate/flow_builder/types.py +57 -3
- ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
- ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
- ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
- 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 +63 -4
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/RECORD +66 -59
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -6,14 +6,15 @@ import logging
|
|
6
6
|
import importlib
|
7
7
|
import inspect
|
8
8
|
from pathlib import Path
|
9
|
-
from typing import List
|
9
|
+
from typing import List, Any
|
10
10
|
|
11
11
|
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import KnowledgeBase
|
12
12
|
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
|
13
13
|
from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
|
14
14
|
from ibm_watsonx_orchestrate.client.connections import get_connections_client
|
15
15
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
16
|
-
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload
|
16
|
+
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload, KnowledgeBaseListEntry
|
17
|
+
from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
|
17
18
|
|
18
19
|
logger = logging.getLogger(__name__)
|
19
20
|
|
@@ -198,8 +199,7 @@ class KnowledgeBaseController:
|
|
198
199
|
|
199
200
|
logger.info(f"Knowledge base '{kb.name}' updated successfully")
|
200
201
|
|
201
|
-
|
202
|
-
def knowledge_base_status( self, id: str, name: str) -> None:
|
202
|
+
def knowledge_base_status( self, id: str, name: str, format: ListFormats = None) -> dict | str | None:
|
203
203
|
knowledge_base_id = self.get_id(id, name)
|
204
204
|
response = self.get_client().status(knowledge_base_id)
|
205
205
|
|
@@ -219,13 +219,25 @@ class KnowledgeBaseController:
|
|
219
219
|
|
220
220
|
response["id"] = kbID
|
221
221
|
|
222
|
+
if format == ListFormats.JSON:
|
223
|
+
return response
|
224
|
+
|
225
|
+
|
222
226
|
[table.add_column(to_column_name(col), {}) for col in response.keys()]
|
223
227
|
table.add_row(*[str(val) for val in response.values()])
|
224
228
|
|
229
|
+
if format == ListFormats.Table:
|
230
|
+
return rich_table_to_markdown(table)
|
231
|
+
|
225
232
|
rich.print(table)
|
226
233
|
|
227
234
|
|
228
|
-
def list_knowledge_bases(self, verbose: bool=False):
|
235
|
+
def list_knowledge_bases(self, verbose: bool=False, format: ListFormats=None)-> List[dict[str, Any]] | List[KnowledgeBaseListEntry] | str | None:
|
236
|
+
|
237
|
+
if verbose and format:
|
238
|
+
logger.error("For knowledge base list, `--verbose` and `--format` are mutually exclusive options")
|
239
|
+
sys.exit(1)
|
240
|
+
|
229
241
|
response = self.get_client().get()
|
230
242
|
knowledge_bases = [KnowledgeBase.model_validate(knowledge_base) for knowledge_base in response]
|
231
243
|
|
@@ -234,7 +246,9 @@ class KnowledgeBaseController:
|
|
234
246
|
for kb in knowledge_bases:
|
235
247
|
knowledge_base_list.append(json.loads(kb.model_dump_json(exclude_none=True)))
|
236
248
|
rich.print(rich.json.JSON(json.dumps(knowledge_base_list, indent=4)))
|
249
|
+
return knowledge_base_list
|
237
250
|
else:
|
251
|
+
knowledge_base_details=[]
|
238
252
|
table = rich.table.Table(
|
239
253
|
show_header=True,
|
240
254
|
header_style="bold white",
|
@@ -251,6 +265,11 @@ class KnowledgeBaseController:
|
|
251
265
|
for column in column_args:
|
252
266
|
table.add_column(column, **column_args[column])
|
253
267
|
|
268
|
+
connections_client = get_connections_client()
|
269
|
+
connections = connections_client.list()
|
270
|
+
|
271
|
+
connections_dict = {conn.connection_id: conn for conn in connections}
|
272
|
+
|
254
273
|
for kb in knowledge_bases:
|
255
274
|
app_id = ""
|
256
275
|
|
@@ -258,18 +277,28 @@ class KnowledgeBaseController:
|
|
258
277
|
and kb.conversational_search_tool.index_config is not None \
|
259
278
|
and len(kb.conversational_search_tool.index_config) > 0 \
|
260
279
|
and kb.conversational_search_tool.index_config[0].connection_id is not None:
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
kb.
|
267
|
-
|
268
|
-
|
280
|
+
conn = connections_dict.get(kb.conversational_search_tool.index_config[0].connection_id)
|
281
|
+
if conn:
|
282
|
+
app_id = conn.app_id
|
283
|
+
|
284
|
+
entry = KnowledgeBaseListEntry(
|
285
|
+
name=kb.name,
|
286
|
+
id=str(kb.id),
|
287
|
+
description=kb.description,
|
288
|
+
app_id=app_id
|
269
289
|
)
|
270
|
-
|
271
|
-
|
272
|
-
|
290
|
+
if format == ListFormats.JSON:
|
291
|
+
knowledge_base_details.append(entry)
|
292
|
+
else:
|
293
|
+
table.add_row(*entry.get_row_details())
|
294
|
+
|
295
|
+
match format:
|
296
|
+
case ListFormats.JSON:
|
297
|
+
return knowledge_base_details
|
298
|
+
case ListFormats.Table:
|
299
|
+
return rich_table_to_markdown(table)
|
300
|
+
case _:
|
301
|
+
rich.print(table)
|
273
302
|
|
274
303
|
def remove_knowledge_base(self, id: str, name: str):
|
275
304
|
knowledge_base_id = self.get_id(id, name)
|
@@ -101,10 +101,11 @@ PROVIDER_EXTRA_PROPERTIES_LUT = {
|
|
101
101
|
PROVIDER_REQUIRED_FIELDS = {k:['api_key'] for k in ModelProvider}
|
102
102
|
# Update required fields for each provider
|
103
103
|
# Use sets to denote when a requirement is 'or'
|
104
|
+
# Tuples denote combined requirements like 'and'
|
104
105
|
PROVIDER_REQUIRED_FIELDS.update({
|
105
106
|
ModelProvider.WATSONX: PROVIDER_REQUIRED_FIELDS[ModelProvider.WATSONX] + [{'watsonx_space_id', 'watsonx_project_id', 'watsonx_deployment_id'}],
|
106
107
|
ModelProvider.OLLAMA: PROVIDER_REQUIRED_FIELDS[ModelProvider.OLLAMA] + ['custom_host'],
|
107
|
-
ModelProvider.BEDROCK: [],
|
108
|
+
ModelProvider.BEDROCK: [{'api_key', ('aws_secret_access_key', 'aws_access_key_id')}],
|
108
109
|
})
|
109
110
|
|
110
111
|
# def env_file_to_model_ProviderConfig(model_name: str, env_file_path: str) -> ProviderConfig | None:
|
@@ -158,16 +159,34 @@ def _validate_extra_fields(provider: ModelProvider, cfg: ProviderConfig) -> None
|
|
158
159
|
if cfg.__dict__.get(attr) is not None and attr not in accepted_fields:
|
159
160
|
logger.warning(f"The config option '{attr}' is not used by provider '{provider}'")
|
160
161
|
|
162
|
+
def _check_credential_provided(cred: str | tuple, provided_creds: set) -> bool:
|
163
|
+
if isinstance(cred, tuple):
|
164
|
+
return all(item in provided_creds for item in cred)
|
165
|
+
else:
|
166
|
+
return cred in provided_creds
|
167
|
+
|
168
|
+
def _format_missing_credential(missing_credential: set) -> str:
|
169
|
+
parts = []
|
170
|
+
for cred in missing_credential:
|
171
|
+
if isinstance(cred, tuple):
|
172
|
+
formatted = " and ".join(f"{x}" for x in cred)
|
173
|
+
parts.append(f"({formatted})")
|
174
|
+
else:
|
175
|
+
parts.append(f"{cred}")
|
176
|
+
|
177
|
+
return " or ".join(parts)
|
178
|
+
|
179
|
+
|
161
180
|
def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id: str = None) -> None:
|
162
181
|
provided_credentials = set([k for k,v in dict(cfg).items() if v is not None])
|
163
182
|
required_creds = PROVIDER_REQUIRED_FIELDS[provider]
|
164
183
|
missing_credentials = []
|
165
184
|
for cred in required_creds:
|
166
185
|
if isinstance(cred, set):
|
167
|
-
if not any(c
|
186
|
+
if not any(_check_credential_provided(c, provided_credentials) for c in cred):
|
168
187
|
missing_credentials.append(cred)
|
169
188
|
else:
|
170
|
-
if
|
189
|
+
if not _check_credential_provided(cred, provided_credentials):
|
171
190
|
missing_credentials.append(cred)
|
172
191
|
|
173
192
|
if len(missing_credentials) > 0:
|
@@ -177,7 +196,7 @@ def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id:
|
|
177
196
|
missing_credentials_string = f"Be sure to include the following required fields for provider '{provider}' in the connection '{app_id}':"
|
178
197
|
for cred in missing_credentials:
|
179
198
|
if isinstance(cred, set):
|
180
|
-
cred_str =
|
199
|
+
cred_str = _format_missing_credential(cred)
|
181
200
|
else:
|
182
201
|
cred_str = cred
|
183
202
|
missing_credentials_string += f"\n\t - {cred_str}"
|
@@ -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
|
@@ -23,6 +23,7 @@ from ibm_watsonx_orchestrate.client.connections import get_connections_client
|
|
23
23
|
from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment
|
24
24
|
from ibm_watsonx_orchestrate.cli.commands.connections.connections_controller import export_connection
|
25
25
|
from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import ToolsController
|
26
|
+
from ibm_watsonx_orchestrate.utils.utils import sanitize_catalog_label
|
26
27
|
from .types import *
|
27
28
|
|
28
29
|
APPLICATIONS_FILE_VERSION = '1.16.0'
|
@@ -54,7 +55,7 @@ def get_tool_bindings(tool_names: list[str]) -> dict[str, dict]:
|
|
54
55
|
|
55
56
|
return results
|
56
57
|
|
57
|
-
def _patch_agent_yamls(project_root: Path, publisher_name: str):
|
58
|
+
def _patch_agent_yamls(project_root: Path, publisher_name: str, parent_agent_name: str):
|
58
59
|
agents_dir = project_root / "agents"
|
59
60
|
if not agents_dir.exists():
|
60
61
|
return
|
@@ -70,15 +71,18 @@ def _patch_agent_yamls(project_root: Path, publisher_name: str):
|
|
70
71
|
if "language_support" not in agent_data:
|
71
72
|
agent_data["language_support"] = ["English"]
|
72
73
|
if "icon" not in agent_data:
|
73
|
-
agent_data["icon"] =
|
74
|
+
agent_data["icon"] = AGENT_CATALOG_ONLY_PLACEHOLDERS['icon']
|
74
75
|
if "category" not in agent_data:
|
75
76
|
agent_data["category"] = "agent"
|
76
77
|
if "supported_apps" not in agent_data:
|
77
78
|
agent_data["supported_apps"] = []
|
79
|
+
if "agent_role" not in agent_data:
|
80
|
+
agent_data["agent_role"] = "manager" if agent_data.get("name") == parent_agent_name else "collaborator"
|
78
81
|
|
79
82
|
with open(agent_yaml, "w") as f:
|
80
83
|
yaml.safe_dump(agent_data, f, sort_keys=False)
|
81
84
|
|
85
|
+
|
82
86
|
def _create_applications_entry(connection_config: dict) -> dict:
|
83
87
|
return {
|
84
88
|
'app_id': connection_config.get('app_id'),
|
@@ -116,6 +120,15 @@ class PartnersOfferingController:
|
|
116
120
|
sys.exit(1)
|
117
121
|
|
118
122
|
def create(self, offering: str, publisher_name: str, agent_type: str, agent_name: str):
|
123
|
+
|
124
|
+
# Sanitize offering name
|
125
|
+
original_offering = offering
|
126
|
+
offering = sanitize_catalog_label(offering)
|
127
|
+
|
128
|
+
if offering != original_offering:
|
129
|
+
logger.warning("Offering name must contain only alpahnumeric characters or underscore")
|
130
|
+
logger.info(f"Offering '{original_offering}' has been updated to '{offering}'")
|
131
|
+
|
119
132
|
# Create parent project folder
|
120
133
|
project_root = self.root / offering
|
121
134
|
|
@@ -179,7 +192,7 @@ class PartnersOfferingController:
|
|
179
192
|
output_zip.unlink(missing_ok=True)
|
180
193
|
|
181
194
|
# Patch the agent yamls with publisher, tags, icon, etc.
|
182
|
-
_patch_agent_yamls(project_root, publisher_name)
|
195
|
+
_patch_agent_yamls(project_root=project_root, publisher_name=publisher_name, parent_agent_name=agent_name)
|
183
196
|
|
184
197
|
|
185
198
|
# Create offering.yaml file -------------------------------------------------------
|
@@ -337,12 +350,16 @@ class PartnersOfferingController:
|
|
337
350
|
**agent_data
|
338
351
|
)
|
339
352
|
agent = Agent.model_validate(agent_details)
|
340
|
-
AgentsController().persist_record(agent=agent)
|
341
353
|
case AgentKind.EXTERNAL:
|
342
354
|
agent_details = parse_create_external_args(
|
343
355
|
**agent_data
|
344
356
|
)
|
345
357
|
agent = ExternalAgent.model_validate(agent_details)
|
358
|
+
|
359
|
+
# Placeholder detection
|
360
|
+
for label,placeholder in AGENT_CATALOG_ONLY_PLACEHOLDERS.items():
|
361
|
+
if agent_data.get(label) == placeholder:
|
362
|
+
logger.warning(f"Placeholder '{label}' detected for agent '{agent_name}', please ensure '{label}' is correct before packaging.")
|
346
363
|
|
347
364
|
agent_json_path = f"{top_level_folder}/agents/{agent_name}/config.json"
|
348
365
|
zf.writestr(agent_json_path, json.dumps(agent_data, indent=2))
|
@@ -24,6 +24,10 @@ CATALOG_ONLY_FIELDS = [
|
|
24
24
|
'supported_apps'
|
25
25
|
]
|
26
26
|
|
27
|
+
AGENT_CATALOG_ONLY_PLACEHOLDERS = {
|
28
|
+
'icon': "inline-svg-of-icon",
|
29
|
+
}
|
30
|
+
|
27
31
|
class AgentKind(str, Enum):
|
28
32
|
NATIVE = "native"
|
29
33
|
EXTERNAL = "external"
|
@@ -87,21 +91,9 @@ class Offering(BaseModel):
|
|
87
91
|
return values
|
88
92
|
|
89
93
|
def validate_ready_for_packaging(self):
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
placholders = False
|
94
|
-
# part numbers
|
95
|
-
if not self.part_number:
|
96
|
-
raise ValueError(f"Offering '{self.name}' does not have valid part numbers")
|
97
|
-
|
98
|
-
for (k,v) in self.part_number.model_dump().items():
|
99
|
-
if v == CATALOG_PLACEHOLDERS['part_number']:
|
100
|
-
logger.warning(f"Placeholder part number detected for platform '{k}', please ensure valid part numbers are entered before packaging.")
|
101
|
-
placholders = True
|
102
|
-
|
103
|
-
if placholders:
|
104
|
-
raise ValueError(f"Offering '{self.name}' cannot be packaged with placeholder values")
|
94
|
+
# Leaving this fn here in case we want to reintroduce validation
|
95
|
+
pass
|
96
|
+
|
105
97
|
|
106
98
|
|
107
99
|
|
@@ -3,7 +3,7 @@ import typer
|
|
3
3
|
from ibm_watsonx_orchestrate.cli.commands.partners import partners_controller
|
4
4
|
from ibm_watsonx_orchestrate.cli.commands.partners.offering.partners_offering_command import partners_offering
|
5
5
|
|
6
|
-
partners_app = typer.Typer(no_args_is_help=True
|
6
|
+
partners_app = typer.Typer(no_args_is_help=True)
|
7
7
|
|
8
8
|
partners_app.add_typer(
|
9
9
|
partners_offering,
|
@@ -16,7 +16,7 @@ from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
|
16
16
|
from ibm_watsonx_orchestrate.cli.commands.environment.environment_controller import _login
|
17
17
|
|
18
18
|
from ibm_watsonx_orchestrate.cli.config import PROTECTED_ENV_NAME, clear_protected_env_credentials_token, Config, \
|
19
|
-
AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER,
|
19
|
+
AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, LICENSE_HEADER, \
|
20
20
|
ENV_ACCEPT_LICENSE
|
21
21
|
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
|
22
22
|
from ibm_watsonx_orchestrate.utils.docker_utils import DockerLoginService, DockerComposeCore, DockerUtils
|
@@ -38,8 +38,12 @@ def refresh_local_credentials() -> None:
|
|
38
38
|
"""
|
39
39
|
Refresh the local credentials
|
40
40
|
"""
|
41
|
-
|
42
|
-
|
41
|
+
try:
|
42
|
+
clear_protected_env_credentials_token()
|
43
|
+
_login(name=PROTECTED_ENV_NAME, apikey=None)
|
44
|
+
|
45
|
+
except:
|
46
|
+
logger.warning("Failed to refresh local credentials, please run `orchestrate env activate local`")
|
43
47
|
|
44
48
|
def run_compose_lite(
|
45
49
|
final_env_file: Path,
|
@@ -48,13 +52,14 @@ def run_compose_lite(
|
|
48
52
|
experimental_with_ibm_telemetry=False,
|
49
53
|
with_doc_processing=False,
|
50
54
|
with_voice=False,
|
55
|
+
with_connections_ui=False,
|
51
56
|
with_langflow=False,
|
52
57
|
) -> None:
|
53
|
-
|
54
|
-
db_tag =
|
58
|
+
env_service.prepare_clean_env(final_env_file)
|
59
|
+
db_tag = env_service.read_env_file(final_env_file).get('DBTAG', None)
|
55
60
|
logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
|
56
61
|
|
57
|
-
compose_core = DockerComposeCore(env_service)
|
62
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
58
63
|
|
59
64
|
# Step 1: Start only the DB container
|
60
65
|
result = compose_core.service_up(service_name="wxo-server-db", friendly_name="WxO Server DB", final_env_file=final_env_file, compose_env=os.environ)
|
@@ -80,6 +85,8 @@ def run_compose_lite(
|
|
80
85
|
profiles.append("docproc")
|
81
86
|
if with_voice:
|
82
87
|
profiles.append("voice")
|
88
|
+
if with_connections_ui:
|
89
|
+
profiles.append("connections-ui")
|
83
90
|
if with_langflow:
|
84
91
|
profiles.append("langflow")
|
85
92
|
|
@@ -194,7 +201,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
|
|
194
201
|
logger.error("Healthcheck failed orchestrate server. Make sure you start the server components with `orchestrate server start` before trying to start the chat UI")
|
195
202
|
return False
|
196
203
|
|
197
|
-
compose_core = DockerComposeCore(env_service)
|
204
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
198
205
|
|
199
206
|
result = compose_core.service_up(service_name="ui", friendly_name="UI", final_env_file=final_env_file)
|
200
207
|
|
@@ -231,7 +238,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
|
|
231
238
|
|
232
239
|
cli_config = Config()
|
233
240
|
env_service = EnvService(cli_config)
|
234
|
-
compose_core = DockerComposeCore(env_service)
|
241
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
235
242
|
|
236
243
|
result = compose_core.service_down(service_name="ui", friendly_name="UI", final_env_file=final_env_file, is_reset=is_reset)
|
237
244
|
|
@@ -252,7 +259,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
|
|
252
259
|
|
253
260
|
cli_config = Config()
|
254
261
|
env_service = EnvService(cli_config)
|
255
|
-
compose_core = DockerComposeCore(env_service)
|
262
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
256
263
|
|
257
264
|
result = compose_core.services_down(final_env_file=final_env_file, is_reset=is_reset)
|
258
265
|
|
@@ -273,7 +280,7 @@ def run_compose_lite_logs(final_env_file: Path) -> None:
|
|
273
280
|
|
274
281
|
cli_config = Config()
|
275
282
|
env_service = EnvService(cli_config)
|
276
|
-
compose_core = DockerComposeCore(env_service)
|
283
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
277
284
|
|
278
285
|
result = compose_core.services_logs(final_env_file=final_env_file, should_follow=True)
|
279
286
|
|
@@ -358,11 +365,14 @@ def server_start(
|
|
358
365
|
'--with-voice', '-v',
|
359
366
|
help='Enable voice controller to interact with the chat via voice channels'
|
360
367
|
),
|
368
|
+
with_connections_ui: bool = typer.Option(
|
369
|
+
False,
|
370
|
+
'--with-connections-ui', '-c',
|
371
|
+
help='Enables connections ui to facilitate OAuth connections and credential management via a UI'),
|
361
372
|
with_langflow: bool = typer.Option(
|
362
373
|
False,
|
363
374
|
'--with-langflow',
|
364
|
-
help='Enable Langflow UI, available at http://localhost:7861'
|
365
|
-
hidden=True
|
375
|
+
help='Enable Langflow UI, available at http://localhost:7861'
|
366
376
|
),
|
367
377
|
):
|
368
378
|
cli_config = Config()
|
@@ -389,7 +399,8 @@ def server_start(
|
|
389
399
|
env_service.set_compose_file_path_in_env(custom_compose_file)
|
390
400
|
|
391
401
|
user_env = env_service.get_user_env(user_env_file=user_env_file, fallback_to_persisted_env=False)
|
392
|
-
env_service.
|
402
|
+
developer_edition_source = env_service.get_dev_edition_source_core(user_env)
|
403
|
+
env_service.persist_user_env(user_env, include_secrets=persist_env_secrets, source=developer_edition_source)
|
393
404
|
|
394
405
|
merged_env_dict = env_service.prepare_server_env_vars(user_env=user_env, should_drop_auth_routes=False)
|
395
406
|
|
@@ -425,6 +436,7 @@ def server_start(
|
|
425
436
|
experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
|
426
437
|
with_doc_processing=with_doc_processing,
|
427
438
|
with_voice=with_voice,
|
439
|
+
with_connections_ui=with_connections_ui,
|
428
440
|
with_langflow=with_langflow, env_service=env_service)
|
429
441
|
|
430
442
|
run_db_migration()
|
@@ -444,10 +456,7 @@ def server_start(
|
|
444
456
|
)
|
445
457
|
exit(1)
|
446
458
|
|
447
|
-
|
448
|
-
refresh_local_credentials()
|
449
|
-
except:
|
450
|
-
logger.warning("Failed to refresh local credentials, please run `orchestrate env activate local`")
|
459
|
+
refresh_local_credentials()
|
451
460
|
|
452
461
|
logger.info(f"You can run `orchestrate env activate local` to set your environment or `orchestrate chat start` to start the UI service and begin chatting.")
|
453
462
|
|
@@ -455,9 +464,10 @@ def server_start(
|
|
455
464
|
logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
|
456
465
|
if with_doc_processing:
|
457
466
|
logger.info(f"Document processing in Flows (Public Preview) has been enabled.")
|
467
|
+
if with_connections_ui:
|
468
|
+
logger.info("Connections UI can be found at http://localhost:3412/connectors")
|
458
469
|
if with_langflow:
|
459
470
|
logger.info("Langflow has been enabled, the Langflow UI is available at http://localhost:7861")
|
460
|
-
|
461
471
|
@server_app.command(name="stop")
|
462
472
|
def server_stop(
|
463
473
|
user_env_file: str = typer.Option(
|
@@ -562,7 +572,7 @@ def run_db_migration() -> None:
|
|
562
572
|
|
563
573
|
cli_config = Config()
|
564
574
|
env_service = EnvService(cli_config)
|
565
|
-
compose_core = DockerComposeCore(env_service)
|
575
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
566
576
|
|
567
577
|
result = compose_core.service_container_bash_exec(service_name="wxo-server-db",
|
568
578
|
log_message="Running Database Migration...",
|
@@ -616,7 +626,7 @@ def create_langflow_db() -> None:
|
|
616
626
|
|
617
627
|
cli_config = Config()
|
618
628
|
env_service = EnvService(cli_config)
|
619
|
-
compose_core = DockerComposeCore(env_service)
|
629
|
+
compose_core = DockerComposeCore(env_service=env_service)
|
620
630
|
|
621
631
|
result = compose_core.service_container_bash_exec(service_name="wxo-server-db",
|
622
632
|
log_message="Preparing Langflow resources...",
|
@@ -47,11 +47,11 @@ def import_toolkit(
|
|
47
47
|
] = None,
|
48
48
|
url: Annotated[
|
49
49
|
Optional[str],
|
50
|
-
typer.Option("--url", "-u", help="The URL of the remote MCP server"
|
50
|
+
typer.Option("--url", "-u", help="The URL of the remote MCP server"),
|
51
51
|
] = None,
|
52
52
|
transport: Annotated[
|
53
53
|
ToolkitTransportKind,
|
54
|
-
typer.Option("--transport", help="The communication protocol to use for the remote MCP server. Only \"sse\" or \"streamable_http\" supported"
|
54
|
+
typer.Option("--transport", help="The communication protocol to use for the remote MCP server. Only \"sse\" or \"streamable_http\" supported"),
|
55
55
|
] = None,
|
56
56
|
tools: Annotated[
|
57
57
|
Optional[str],
|