ibm-watsonx-orchestrate 1.4.2__py3-none-any.whl → 1.5.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.
Files changed (40) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +10 -1
  3. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +14 -0
  4. ibm_watsonx_orchestrate/agent_builder/model_policies/__init__.py +1 -0
  5. ibm_watsonx_orchestrate/{client → agent_builder}/model_policies/types.py +7 -8
  6. ibm_watsonx_orchestrate/agent_builder/models/__init__.py +1 -0
  7. ibm_watsonx_orchestrate/{client → agent_builder}/models/types.py +57 -9
  8. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +46 -3
  9. ibm_watsonx_orchestrate/agent_builder/tools/types.py +47 -1
  10. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +17 -0
  11. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +86 -39
  12. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +191 -0
  13. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +140 -258
  14. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +437 -0
  15. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +2 -1
  16. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +1 -1
  17. ibm_watsonx_orchestrate/client/connections/__init__.py +2 -1
  18. ibm_watsonx_orchestrate/client/connections/utils.py +30 -0
  19. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +23 -4
  20. ibm_watsonx_orchestrate/client/models/models_client.py +23 -3
  21. ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py +13 -8
  22. ibm_watsonx_orchestrate/client/tools/tool_client.py +2 -1
  23. ibm_watsonx_orchestrate/docker/compose-lite.yml +2 -0
  24. ibm_watsonx_orchestrate/docker/default.env +10 -11
  25. ibm_watsonx_orchestrate/experimental/flow_builder/data_map.py +19 -0
  26. ibm_watsonx_orchestrate/experimental/flow_builder/flows/__init__.py +4 -3
  27. ibm_watsonx_orchestrate/experimental/flow_builder/flows/constants.py +3 -1
  28. ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +3 -2
  29. ibm_watsonx_orchestrate/experimental/flow_builder/flows/flow.py +245 -223
  30. ibm_watsonx_orchestrate/experimental/flow_builder/node.py +34 -15
  31. ibm_watsonx_orchestrate/experimental/flow_builder/resources/flow_status.openapi.yml +7 -39
  32. ibm_watsonx_orchestrate/experimental/flow_builder/types.py +285 -12
  33. ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +3 -1
  34. {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b1.dist-info}/METADATA +1 -1
  35. {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b1.dist-info}/RECORD +38 -35
  36. ibm_watsonx_orchestrate/cli/commands/models/env_file_model_provider_mapper.py +0 -180
  37. ibm_watsonx_orchestrate/experimental/flow_builder/flows/data_map.py +0 -91
  38. {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b1.dist-info}/WHEEL +0 -0
  39. {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b1.dist-info}/entry_points.txt +0 -0
  40. {ibm_watsonx_orchestrate-1.4.2.dist-info → ibm_watsonx_orchestrate-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -1,32 +1,20 @@
1
1
  import logging
2
- import os
3
- import sys
4
2
  from typing import List
3
+ import json
4
+ import sys
5
5
 
6
- import requests
7
- import rich
8
- import rich.highlighter
9
6
  import typer
10
7
  from typing_extensions import Annotated
11
8
 
12
- from ibm_watsonx_orchestrate.cli.commands.server.server_command import get_default_env_file, merge_env
13
- from ibm_watsonx_orchestrate.client.model_policies.model_policies_client import ModelPoliciesClient
14
- from ibm_watsonx_orchestrate.client.model_policies.types import ModelPolicy, ModelPolicyInner, \
15
- ModelPolicyRetry, ModelPolicyStrategy, ModelPolicyStrategyMode, ModelPolicyTarget
16
- from ibm_watsonx_orchestrate.client.models.models_client import ModelsClient
17
- from ibm_watsonx_orchestrate.client.models.types import CreateVirtualModel, ANTHROPIC_DEFAULT_MAX_TOKENS
18
- from ibm_watsonx_orchestrate.client.utils import instantiate_client
9
+ from ibm_watsonx_orchestrate.agent_builder.models.types import ModelType
10
+ from ibm_watsonx_orchestrate.agent_builder.model_policies.types import ModelPolicyStrategyMode
11
+ from ibm_watsonx_orchestrate.cli.commands.models.models_controller import ModelsController
12
+
19
13
 
20
14
  logger = logging.getLogger(__name__)
21
15
  models_app = typer.Typer(no_args_is_help=True)
22
16
  models_policy_app = typer.Typer(no_args_is_help=True)
23
- # models_app.add_typer(models_policy_app, name='policy', help='Add or remove pseudo models which route traffic between multiple downstream models')
24
-
25
- WATSONX_URL = os.getenv("WATSONX_URL")
26
-
27
- class ModelHighlighter(rich.highlighter.RegexHighlighter):
28
- base_style = "model."
29
- highlights = [r"(?P<name>(watsonx|virtual[-]model|virtual[-]policy)\/.+\/.+):"]
17
+ models_app.add_typer(models_policy_app, name='policy', help='Add or remove pseudo models which route traffic between multiple downstream models')
30
18
 
31
19
  @models_app.command(name="list", help="List available models")
32
20
  def model_list(
@@ -35,108 +23,33 @@ def model_list(
35
23
  typer.Option("--raw", "-r", help="Display the list of models in a non-tabular format"),
36
24
  ] = False,
37
25
  ):
38
- models_client: ModelsClient = instantiate_client(ModelsClient)
39
- model_policies_client: ModelPoliciesClient = instantiate_client(ModelPoliciesClient)
40
- global WATSONX_URL
41
- default_env_path = get_default_env_file()
42
- merged_env_dict = merge_env(
43
- default_env_path,
44
- None
45
- )
46
-
47
- if 'WATSONX_URL' in merged_env_dict and merged_env_dict['WATSONX_URL']:
48
- WATSONX_URL = merged_env_dict['WATSONX_URL']
49
-
50
- watsonx_url = merged_env_dict.get("WATSONX_URL")
51
- if not watsonx_url:
52
- logger.error("Error: WATSONX_URL is required in the environment.")
53
- sys.exit(1)
54
-
55
- logger.info("Retrieving virtual-model models list...")
56
- virtual_models = models_client.list()
57
-
58
-
59
-
60
- logger.info("Retrieving virtual-policies models list...")
61
- virtual_model_policies = model_policies_client.list()
62
-
63
- logger.info("Retrieving watsonx.ai models list...")
64
- found_models = _get_wxai_foundational_models()
65
-
66
-
67
- preferred_str = merged_env_dict.get('PREFERRED_MODELS', '')
68
- incompatible_str = merged_env_dict.get('INCOMPATIBLE_MODELS', '')
69
-
70
- preferred_list = _string_to_list(preferred_str)
71
- incompatible_list = _string_to_list(incompatible_str)
72
-
73
- models = found_models.get("resources", [])
74
- if not models:
75
- logger.error("No models found.")
76
- else:
77
- # Remove incompatible models
78
- filtered_models = []
79
- for model in models:
80
- model_id = model.get("model_id", "")
81
- short_desc = model.get("short_description", "")
82
- if any(incomp in model_id.lower() for incomp in incompatible_list):
83
- continue
84
- if any(incomp in short_desc.lower() for incomp in incompatible_list):
85
- continue
86
- filtered_models.append(model)
87
-
88
- # Sort to put the preferred first
89
- def sort_key(model):
90
- model_id = model.get("model_id", "").lower()
91
- is_preferred = any(pref in model_id for pref in preferred_list)
92
- return (0 if is_preferred else 1, model_id)
93
-
94
- sorted_models = sorted(filtered_models, key=sort_key)
95
-
96
- if print_raw:
97
- theme = rich.theme.Theme({"model.name": "bold cyan"})
98
- console = rich.console.Console(highlighter=ModelHighlighter(), theme=theme)
99
- console.print("[bold]Available Models:[/bold]")
100
-
101
- for model in virtual_models:
102
- console.print(f"- ✨️ {model.name}:", model.description or 'No description provided.')
103
-
104
- for model in virtual_model_policies:
105
- console.print(f"- ✨️ {model.name}:", 'No description provided.')
106
-
107
- for model in sorted_models:
108
- model_id = model.get("model_id", "N/A")
109
- short_desc = model.get("short_description", "No description provided.")
110
- full_model_name = f"watsonx/{model_id}: {short_desc}"
111
- marker = "★ " if any(pref in model_id.lower() for pref in preferred_list) else ""
112
- console.print(f"- [yellow]{marker}[/yellow]{full_model_name}")
113
-
114
- 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]" )
115
- else:
116
- table = rich.table.Table(
117
- show_header=True,
118
- title="[bold]Available Models[/bold]",
119
- caption="[yellow]★ [/yellow] indicates a supported and preferred model from watsonx\n[blue]✨️[/blue] indicates a model from a custom provider",
120
- show_lines=True)
121
- columns = ["Model", "Description"]
122
- for col in columns:
123
- table.add_column(col)
124
-
125
- for model in virtual_models:
126
- table.add_row(f"✨️ {model.name}", model.description or 'No description provided.')
127
-
128
- for model in virtual_model_policies:
129
- table.add_row(f"✨️ {model.name}", 'No description provided.')
130
-
131
- for model in sorted_models:
132
- model_id = model.get("model_id", "N/A")
133
- short_desc = model.get("short_description", "No description provided.")
134
- marker = "★ " if any(pref in model_id.lower() for pref in preferred_list) else ""
135
- table.add_row(f"[yellow]{marker}[/yellow]watsonx/{model_id}", short_desc)
136
-
137
- rich.print(table)
138
-
26
+ models_controller = ModelsController()
27
+ models_controller.list_models(print_raw=print_raw)
139
28
 
29
+ @models_app.command(name="import", help="Import models from spec file")
30
+ def models_import(
31
+ file: Annotated[
32
+ str,
33
+ typer.Option(
34
+ "--file",
35
+ "-f",
36
+ help="Path to spec file containing model details.",
37
+ ),
38
+ ],
39
+ app_id: Annotated[
40
+ str, typer.Option(
41
+ '--app-id', '-a',
42
+ help='The app id of a key_value connection containing authentications details for the model provider.'
43
+ )
44
+ ] = None,
45
+ ):
46
+ models_controller = ModelsController()
47
+ models = models_controller.import_model(
48
+ file=file,
49
+ app_id=app_id
50
+ )
51
+ for model in models:
52
+ models_controller.publish_or_update_models(model=model)
140
53
 
141
54
  @models_app.command(name="add", help="Add an llm from a custom provider")
142
55
  def models_add(
@@ -144,10 +57,6 @@ def models_add(
144
57
  str,
145
58
  typer.Option("--name", "-n", help="The name of the model to add"),
146
59
  ],
147
- env_file: Annotated[
148
- str,
149
- typer.Option('--env-file', '-e', help='The path to an .env file containing the credentials for your llm provider'),
150
- ],
151
60
  description: Annotated[
152
61
  str,
153
62
  typer.Option('--description', '-d', help='The description of the model to add'),
@@ -156,33 +65,42 @@ def models_add(
156
65
  str,
157
66
  typer.Option('--display-name', help='What name should this llm appear as within the ui'),
158
67
  ] = None,
159
-
68
+ provider_config: Annotated[
69
+ str,
70
+ typer.Option(
71
+ "--provider-config",
72
+ help="LLM provider configuration in JSON format (e.g., '{\"customHost\": \"xyz\"}')",
73
+ ),
74
+ ] = None,
75
+ app_id: Annotated[
76
+ str, typer.Option(
77
+ '--app-id', '-a',
78
+ help='The app id of a key_value connection containing authentications details for the model provider.'
79
+ )
80
+ ] = None,
81
+ type: Annotated[
82
+ ModelType,
83
+ typer.Option('--type', help='What type of model is it'),
84
+ ] = ModelType.CHAT,
160
85
  ):
161
- from ibm_watsonx_orchestrate.cli.commands.models.env_file_model_provider_mapper import env_file_to_model_ProviderConfig # lazily import this because the lut building is expensive
162
-
163
- models_client: ModelsClient = instantiate_client(ModelsClient)
164
- provider_config = env_file_to_model_ProviderConfig(model_name=name, env_file_path=env_file)
165
- if not name.startswith('virtual-model/'):
166
- name = f"virtual-model/{name}"
167
-
168
- config=None
169
- # Anthropic has no default for max_tokens
170
- if "anthropic" in name:
171
- config = {
172
- "max_tokens": ANTHROPIC_DEFAULT_MAX_TOKENS
173
- }
174
-
175
- model = CreateVirtualModel(
86
+ provider_config_dict = {}
87
+ if provider_config:
88
+ try:
89
+ provider_config_dict = json.loads(provider_config)
90
+ except:
91
+ logger.error(f"Failed to parse provider config. '{provider_config}' is not valid json")
92
+ sys.exit(1)
93
+
94
+ models_controller = ModelsController()
95
+ model = models_controller.create_model(
176
96
  name=name,
177
- display_name=display_name or name,
178
97
  description=description,
179
- tags=[],
180
- provider_config=provider_config,
181
- config=config
98
+ display_name=display_name,
99
+ provider_config_dict = provider_config_dict,
100
+ model_type=type,
101
+ app_id=app_id,
182
102
  )
183
-
184
- models_client.create(model)
185
- logger.info(f"Successfully added the model '{name}'")
103
+ models_controller.publish_or_update_models(model=model)
186
104
 
187
105
 
188
106
 
@@ -193,122 +111,86 @@ def models_remove(
193
111
  typer.Option("--name", "-n", help="The name of the model to remove"),
194
112
  ]
195
113
  ):
196
- models_client: ModelsClient = instantiate_client(ModelsClient)
197
- models = models_client.list()
198
- model = next(filter(lambda x: x.name == name or x.name == f"virtual-model/{name}", models), None)
199
- if not model:
200
- logger.error(f"No model found with the name '{name}'")
201
- sys.exit(1)
202
-
203
- models_client.delete(model_id=model.id)
204
- logger.info(f"Successfully removed the model '{name}'")
205
-
206
-
207
- # @models_policy_app.command(name='add', help='Add a model policy')
208
- # def models_policy_add(
209
- # name: Annotated[
210
- # str,
211
- # typer.Option("--name", "-n", help="The name of the model to remove"),
212
- # ],
213
- # models: Annotated[
214
- # List[str],
215
- # typer.Option('--model', '-m', help='The name of the model to add'),
216
- # ],
217
- # strategy: Annotated[
218
- # ModelPolicyStrategyMode,
219
- # typer.Option('--strategy', '-s', help='How to spread traffic across models'),
220
- # ],
221
- # strategy_on_code: Annotated[
222
- # List[int],
223
- # typer.Option('--strategy-on-code', help='The http status to consider invoking the strategy'),
224
- # ],
225
- # retry_on_code: Annotated[
226
- # List[int],
227
- # typer.Option('--retry-on-code', help='The http status to consider retrying the llm call'),
228
- # ],
229
- # retry_attempts: Annotated[
230
- # int,
231
- # typer.Option('--retry-attempts', help='The number of attempts to retry'),
232
- # ],
233
- # display_name: Annotated[
234
- # str,
235
- # typer.Option('--display-name', help='What name should this llm appear as within the ui'),
236
- # ] = None
237
- # ):
238
- # model_policies_client: ModelPoliciesClient = instantiate_client(ModelPoliciesClient)
239
- # model_client: ModelsClient = instantiate_client(ModelsClient)
240
- # model_lut = {m.name: m.id for m in model_client.list()}
241
- # for m in models:
242
- # if m not in model_lut:
243
- # logger.error(f"No model found with the name '{m}'")
244
- # exit(1)
245
-
246
- # inner = ModelPolicyInner()
247
- # inner.strategy = ModelPolicyStrategy(
248
- # mode=strategy,
249
- # on_status_codes=strategy_on_code
250
- # )
251
- # inner.targets = [ModelPolicyTarget(model_id=model_lut[m], weight=1) for m in models]
252
- # if retry_on_code:
253
- # inner.retry = ModelPolicyRetry(
254
- # on_status_codes=retry_on_code,
255
- # attempts=retry_attempts
256
- # )
257
-
258
- # if not display_name:
259
- # display_name = name
260
-
261
-
262
- # policy = ModelPolicy(
263
- # name=name,
264
- # display_name=display_name,
265
- # policy=inner
266
- # )
267
- # model_policies_client.create(policy)
268
- # logger.info(f"Successfully added the model policy '{name}'")
269
-
270
-
271
-
272
- # @models_policy_app.command(name='remove', help='Remove a model policy')
273
- # def models_policy_remove(
274
- # name: Annotated[
275
- # str,
276
- # typer.Option("--name", "-n", help="The name of the model policy to remove"),
277
- # ]
278
- # ):
279
- # model_policies_client: ModelPoliciesClient = instantiate_client(ModelPoliciesClient)
280
- # model_policies = model_policies_client.list()
281
-
282
- # policy = next(filter(lambda x: x.name == name or x.name == f"virtual-policy/{name}", model_policies), None)
283
- # if not policy:
284
- # logger.error(f"No model found with the name '{name}'")
285
- # exit(1)
286
-
287
- # model_policies_client.delete(model_policy_id=policy.id)
288
- # logger.info(f"Successfully removed the model '{name}'")
114
+ models_controller = ModelsController()
115
+ models_controller.remove_model(name=name)
289
116
 
117
+ @models_policy_app.command(name='import', help='Add a model policy')
118
+ def models_policy_import(
119
+ file: Annotated[
120
+ str,
121
+ typer.Option(
122
+ "--file",
123
+ "-f",
124
+ help="Path to spec file containing model details.",
125
+ ),
126
+ ],
127
+ ):
128
+ models_controller = ModelsController()
129
+ policies = models_controller.import_model_policy(
130
+ file=file
131
+ )
132
+ for policy in policies:
133
+ models_controller.publish_or_update_model_policies(policy=policy)
290
134
 
291
- def _get_wxai_foundational_models():
292
- foundation_models_url = WATSONX_URL + "/ml/v1/foundation_model_specs?version=2024-05-01"
135
+ @models_policy_app.command(name='add', help='Add a model policy')
136
+ def models_policy_add(
137
+ name: Annotated[
138
+ str,
139
+ typer.Option("--name", "-n", help="The name of the model to remove"),
140
+ ],
141
+ models: Annotated[
142
+ List[str],
143
+ typer.Option('--model', '-m', help='The name of the model to add'),
144
+ ],
145
+ strategy: Annotated[
146
+ ModelPolicyStrategyMode,
147
+ typer.Option('--strategy', '-s', help='How to spread traffic across models'),
148
+ ],
149
+ strategy_on_code: Annotated[
150
+ List[int],
151
+ typer.Option('--strategy-on-code', help='The http status to consider invoking the strategy'),
152
+ ],
153
+ retry_on_code: Annotated[
154
+ List[int],
155
+ 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
+ ],
161
+ display_name: Annotated[
162
+ str,
163
+ typer.Option('--display-name', help='What name should this llm appear as within the ui'),
164
+ ] = None,
165
+ description: Annotated[
166
+ str,
167
+ typer.Option('--description', help='Description of the policy for display in the ui'),
168
+ ] = None
169
+ ):
170
+ models_controller = ModelsController()
171
+ policy = models_controller.create_model_policy(
172
+ name=name,
173
+ models=models,
174
+ strategy=strategy,
175
+ strategy_on_code=strategy_on_code,
176
+ retry_on_code=retry_on_code,
177
+ retry_attempts=retry_attempts,
178
+ display_name=display_name,
179
+ description=description
180
+ )
181
+ models_controller.publish_or_update_model_policies(policy=policy)
293
182
 
294
- try:
295
- response = requests.get(foundation_models_url)
296
- except requests.exceptions.RequestException as e:
297
- logger.exception(f"Exception when connecting to Watsonx URL: {foundation_models_url}")
298
- raise
299
183
 
300
- if response.status_code != 200:
301
- error_message = (
302
- f"Failed to retrieve foundational models from {foundation_models_url}. "
303
- f"Status code: {response.status_code}. Response: {response.content}"
304
- )
305
- raise Exception(error_message)
306
-
307
- json_response = response.json()
308
- return json_response
309
184
 
310
- def _string_to_list(env_value):
311
- return [item.strip().lower() for item in env_value.split(",") if item.strip()]
185
+ @models_policy_app.command(name='remove', help='Remove a model policy')
186
+ def models_policy_remove(
187
+ name: Annotated[
188
+ str,
189
+ typer.Option("--name", "-n", help="The name of the model policy to remove"),
190
+ ]
191
+ ):
192
+ models_controller = ModelsController()
193
+ models_controller.remove_policy(name=name)
312
194
 
313
195
  if __name__ == "__main__":
314
196
  models_app()