ibm-watsonx-orchestrate 1.8.0b0__py3-none-any.whl → 1.9.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.
Files changed (32) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +12 -0
  3. ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -2
  4. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +61 -11
  5. ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -2
  6. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +3 -3
  7. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
  8. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +7 -7
  9. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  10. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -8
  11. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +111 -36
  12. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +23 -7
  13. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -1
  14. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +102 -37
  15. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +20 -2
  16. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +10 -8
  17. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
  18. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +2 -10
  19. ibm_watsonx_orchestrate/client/connections/connections_client.py +5 -30
  20. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +2 -1
  21. ibm_watsonx_orchestrate/client/utils.py +22 -20
  22. ibm_watsonx_orchestrate/docker/compose-lite.yml +12 -5
  23. ibm_watsonx_orchestrate/docker/default.env +13 -12
  24. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +8 -5
  25. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +47 -7
  26. ibm_watsonx_orchestrate/flow_builder/node.py +7 -1
  27. ibm_watsonx_orchestrate/flow_builder/types.py +168 -65
  28. {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/METADATA +2 -2
  29. {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/RECORD +32 -32
  30. {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/WHEEL +0 -0
  31. {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/entry_points.txt +0 -0
  32. {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@
5
5
 
6
6
  pkg_name = "ibm-watsonx-orchestrate"
7
7
 
8
- __version__ = "1.8.0b0"
8
+ __version__ = "1.9.0b0"
9
9
 
10
10
 
11
11
 
@@ -29,6 +29,12 @@ yaml.representer.SafeRepresenter.add_representer(str, str_presenter) # to use wi
29
29
  class SpecVersion(str, Enum):
30
30
  V1 = "v1"
31
31
 
32
+ def __str__(self):
33
+ return self.value
34
+
35
+ def __repr__(self):
36
+ return repr(self.value)
37
+
32
38
 
33
39
  class AgentKind(str, Enum):
34
40
  NATIVE = "native"
@@ -104,6 +110,12 @@ class AgentStyle(str, Enum):
104
110
  REACT = "react"
105
111
  PLANNER = "planner"
106
112
 
113
+ def __str__(self):
114
+ return self.value
115
+
116
+ def __repr__(self):
117
+ return repr(self.value)
118
+
107
119
  class AgentGuideline(BaseModel):
108
120
  model_config = ConfigDict(arbitrary_types_allowed=True)
109
121
 
@@ -75,6 +75,16 @@ class ConnectionType(str, Enum):
75
75
 
76
76
  def __repr__(self):
77
77
  return repr(self.value)
78
+
79
+ class ConnectionSendVia(str,Enum):
80
+ HEADER = 'header'
81
+ BODY = 'body'
82
+
83
+ def __str__(self):
84
+ return self.value
85
+
86
+ def __repr__(self):
87
+ return repr(self.value)
78
88
 
79
89
  OAUTH_CONNECTION_TYPES = {
80
90
  ConnectionType.OAUTH2_AUTH_CODE,
@@ -177,7 +187,7 @@ class OAuth2AuthCodeCredentials(BaseModel):
177
187
  client_secret: str
178
188
  token_url: str
179
189
  authorization_url: str
180
- scopes : Optional[List[str]] = None
190
+ scope : Optional[str] = None
181
191
 
182
192
  # class OAuth2ImplicitCredentials(BaseModel):
183
193
  # client_id: str
@@ -193,7 +203,9 @@ class OAuth2ClientCredentials(BaseModel):
193
203
  client_id: str
194
204
  client_secret: str
195
205
  token_url: str
196
- scopes : Optional[List[str]] = None
206
+ scope : Optional[str] = None
207
+ send_via: ConnectionSendVia = ConnectionSendVia.HEADER
208
+ grant_type: str = "client_credentials"
197
209
 
198
210
  class OAuthOnBehalfOfCredentials(BaseModel):
199
211
  client_id: str
@@ -14,7 +14,7 @@ from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
14
14
  from .types import ToolSpec
15
15
  from .base_tool import BaseTool
16
16
  from .types import HTTP_METHOD, ToolPermission, ToolRequestBody, ToolResponseBody, \
17
- OpenApiToolBinding, \
17
+ OpenApiToolBinding, AcknowledgementBinding, \
18
18
  JsonSchemaObject, ToolBinding, OpenApiSecurityScheme, CallbackBinding
19
19
 
20
20
  import json
@@ -207,35 +207,82 @@ def create_openapi_json_tool(
207
207
 
208
208
  # If it's an async tool, add callback binding
209
209
  if spec.is_async:
210
-
211
-
212
210
  callbacks = route_spec.get('callbacks', {})
213
211
  callback_name = next(iter(callbacks.keys()))
214
212
  callback_spec = callbacks[callback_name]
215
213
  callback_path = next(iter(callback_spec.keys()))
216
214
  callback_method = next(iter(callback_spec[callback_path].keys()))
215
+ callback_operation = callback_spec[callback_path][callback_method]
217
216
 
218
- # Phase 1: Create a separate input schema for callback that excludes callbackUrl
219
- # Note: Currently assuming the callback URL parameter will be named 'callbackUrl' in the OpenAPI spec
220
- # Future phases will handle other naming conventions
217
+ # Extract callback input schema from the callback requestBody
221
218
  callback_input_schema = ToolRequestBody(
219
+ type='object',
220
+ properties={},
221
+ required=[]
222
+ )
223
+
224
+ # Handle callback parameters (query, path, header params)
225
+ callback_parameters = callback_operation.get('parameters') or []
226
+ for parameter in callback_parameters:
227
+ name = f"{parameter['in']}_{parameter['name']}"
228
+ if parameter.get('required'):
229
+ callback_input_schema.required.append(name)
230
+ parameter['schema']['title'] = parameter['name']
231
+ parameter['schema']['description'] = parameter.get('description', None)
232
+ callback_input_schema.properties[name] = JsonSchemaObject.model_validate(parameter['schema'])
233
+ callback_input_schema.properties[name].in_field = parameter['in']
234
+ callback_input_schema.properties[name].aliasName = parameter['name']
235
+
236
+ # Handle callback request body
237
+ callback_request_body_params = callback_operation.get('requestBody', {}).get('content', {}).get(http_response_content_type, {}).get('schema', None)
238
+ if callback_request_body_params is not None:
239
+ callback_input_schema.required.append('__requestBody__')
240
+ callback_request_body_params = copy.deepcopy(callback_request_body_params)
241
+ callback_request_body_params['in'] = 'body'
242
+ if callback_request_body_params.get('title') is None:
243
+ callback_request_body_params['title'] = 'CallbackRequestBody'
244
+ if callback_request_body_params.get('description') is None:
245
+ callback_request_body_params['description'] = 'The callback request body used for this async operation.'
246
+
247
+ callback_input_schema.properties['__requestBody__'] = JsonSchemaObject.model_validate(callback_request_body_params)
248
+
249
+ # Extract callback output schema
250
+ callback_responses = callback_operation.get('responses', {})
251
+ callback_response = callback_responses.get(str(http_success_response_code), {})
252
+ callback_response_description = callback_response.get('description')
253
+ callback_response_schema = callback_response.get('content', {}).get(http_response_content_type, {}).get('schema', {})
254
+
255
+ callback_response_schema['required'] = []
256
+ callback_output_schema = ToolResponseBody.model_validate(callback_response_schema)
257
+ callback_output_schema.description = callback_response_description
258
+
259
+ # Remove callbackUrl parameter from main tool input schema
260
+ original_input_schema = ToolRequestBody(
222
261
  type='object',
223
262
  properties={k: v for k, v in spec.input_schema.properties.items() if not k.endswith('_callbackUrl')},
224
263
  required=[r for r in spec.input_schema.required if not r.endswith('_callbackUrl')]
225
264
  )
265
+ spec.input_schema = original_input_schema
226
266
 
227
- if callback_input_schema:
228
- spec.input_schema = callback_input_schema
229
-
267
+ original_response_schema = spec.output_schema
268
+
230
269
  callback_binding = CallbackBinding(
231
270
  callback_url=callback_path,
232
271
  method=callback_method.upper(),
233
- input_schema=callback_input_schema,
234
- output_schema=spec.output_schema
272
+ output_schema=callback_output_schema
235
273
  )
236
274
 
275
+ # Create acknowledgement binding with the original response schema
276
+ acknowledgement_binding = AcknowledgementBinding(
277
+ output_schema=original_response_schema
278
+ )
279
+
280
+ # For async tools, set the main tool's output_schema to the callback's input_schema
281
+ spec.output_schema = callback_input_schema
282
+
237
283
  else:
238
284
  callback_binding = None
285
+ acknowledgement_binding = None
239
286
 
240
287
  openapi_binding = OpenApiToolBinding(
241
288
  http_path=http_path,
@@ -248,6 +295,9 @@ def create_openapi_json_tool(
248
295
  if callback_binding is not None:
249
296
  openapi_binding.callback = callback_binding
250
297
 
298
+ if acknowledgement_binding is not None:
299
+ openapi_binding.acknowledgement = acknowledgement_binding
300
+
251
301
  spec.binding = ToolBinding(openapi=openapi_binding)
252
302
 
253
303
  return OpenAPITool(spec=spec)
@@ -97,9 +97,13 @@ HTTP_METHOD = Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
97
97
  class CallbackBinding(BaseModel):
98
98
  callback_url: str
99
99
  method: HTTP_METHOD
100
- input_schema: ToolRequestBody
100
+ input_schema: Optional[ToolRequestBody] = None
101
101
  output_schema: ToolResponseBody
102
102
 
103
+ class AcknowledgementBinding(BaseModel):
104
+ output_schema: ToolResponseBody
105
+
106
+
103
107
  class OpenApiToolBinding(BaseModel):
104
108
  http_method: HTTP_METHOD
105
109
  http_path: str
@@ -107,7 +111,8 @@ class OpenApiToolBinding(BaseModel):
107
111
  security: Optional[List[OpenApiSecurityScheme]] = None
108
112
  servers: Optional[List[str]] = None
109
113
  connection_id: str | None = None
110
- callback: CallbackBinding = None
114
+ callback: Optional[CallbackBinding] = None
115
+ acknowledgement: Optional[AcknowledgementBinding] = None
111
116
 
112
117
  @model_validator(mode='after')
113
118
  def validate_openapi_tool_binding(self):
@@ -769,7 +769,7 @@ class AgentsController:
769
769
  for agent in native_agents:
770
770
  agents_list.append(json.loads(agent.dumps_spec()))
771
771
 
772
- rich.print(rich.json.JSON(json.dumps(agents_list, indent=4)))
772
+ rich.print_json(json.dumps(agents_list, indent=4))
773
773
  else:
774
774
  native_table = rich.table.Table(
775
775
  show_header=True,
@@ -832,7 +832,7 @@ class AgentsController:
832
832
  if verbose:
833
833
  for agent in external_agents:
834
834
  external_agents_list.append(json.loads(agent.dumps_spec()))
835
- rich.print(rich.json.JSON(json.dumps(external_agents_list, indent=4)))
835
+ rich.print_json(json.dumps(external_agents_list, indent=4))
836
836
  else:
837
837
  external_table = rich.table.Table(
838
838
  show_header=True,
@@ -900,7 +900,7 @@ class AgentsController:
900
900
 
901
901
  if verbose:
902
902
  for agent in assistant_agents:
903
- rich.print(agent.dumps_spec())
903
+ rich.print_json(agent.dumps_spec())
904
904
  else:
905
905
  assistants_table = rich.table.Table(
906
906
  show_header=True,
@@ -1,6 +1,6 @@
1
1
  from enum import Enum
2
2
 
3
- class EnvironmentType(str, Enum):
3
+ class EnvironmentType(str, Enum):
4
4
  DRAFT ='draft'
5
5
  LIVE = 'live'
6
6
 
@@ -8,8 +8,21 @@ class EnvironmentType(str, Enum):
8
8
  return self.value
9
9
 
10
10
 
11
- class ChannelType(str, Enum):
11
+ class ChannelType(str, Enum):
12
12
  WEBCHAT ='webchat'
13
13
 
14
14
  def __str__(self):
15
+ return self.value
16
+
17
+
18
+ class RuntimeEnvironmentType(str, Enum):
19
+ LOCAL = 'local'
20
+ CPD = 'cpd'
21
+ IBM_CLOUD = 'ibmcloud'
22
+ AWS = 'aws'
23
+
24
+ def __str__(self):
25
+ return self.value
26
+
27
+ def __repr__(self):
15
28
  return self.value
@@ -4,12 +4,14 @@ import jwt
4
4
  import sys
5
5
 
6
6
  from ibm_watsonx_orchestrate.cli.config import Config, ENV_WXO_URL_OPT, ENVIRONMENTS_SECTION_HEADER, CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, CHAT_UI_PORT
7
+ from ibm_watsonx_orchestrate.cli.commands.channels.types import RuntimeEnvironmentType
7
8
  from ibm_watsonx_orchestrate.client.utils import is_local_dev, is_ibm_cloud_platform, get_environment, get_cpd_instance_id_from_url, is_saas_env, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT, AUTH_CONFIG_FILE
8
9
 
9
10
  from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
10
11
 
11
12
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
12
13
 
14
+
13
15
  logger = logging.getLogger(__name__)
14
16
 
15
17
  class ChannelsWebchatController:
@@ -95,8 +97,6 @@ class ChannelsWebchatController:
95
97
  if target_env == 'draft' and is_saas == True:
96
98
  logger.error(f'For SAAS, please ensure this agent exists in a Live Environment')
97
99
  exit(1)
98
-
99
-
100
100
 
101
101
  return filtered_environments[0].get("id")
102
102
 
@@ -158,13 +158,13 @@ class ChannelsWebchatController:
158
158
  environment = get_environment()
159
159
 
160
160
  match (environment):
161
- case "local":
161
+ case RuntimeEnvironmentType.LOCAL:
162
162
  tenant_id = self.get_tenant_id_local()
163
163
 
164
- case "cpd":
164
+ case RuntimeEnvironmentType.CPD:
165
165
  tenant_id = get_cpd_instance_id_from_url()
166
166
 
167
- case "ibmcloud":
167
+ case RuntimeEnvironmentType.IBM_CLOUD:
168
168
  crn = input("Please enter your CRN which can be retrieved from the IBM Cloud UI: ")
169
169
  if crn == "":
170
170
  logger.error("You must enter your CRN for IBM Cloud instances")
@@ -175,13 +175,13 @@ class ChannelsWebchatController:
175
175
  sys.exit(1)
176
176
  tenant_id = self.extract_tenant_id_from_crn(crn)
177
177
 
178
- case "ga":
178
+ case RuntimeEnvironmentType.AWS:
179
179
  tenant_id = self.get_tenant_id()
180
180
 
181
181
  case _:
182
182
  logger.error("Environment not recognized")
183
183
  sys.exit(1)
184
-
184
+
185
185
  host_url = self.get_host_url()
186
186
  agent_id = self.get_agent_id(self.agent_name)
187
187
  agent_env_id = self.get_environment_id(self.agent_name, self.env)
@@ -201,6 +201,13 @@ def set_credentials_connection_command(
201
201
  help='For oauth_auth_client_credentials_flow, the client_secret to authenticate with'
202
202
  )
203
203
  ] = None,
204
+ send_via: Annotated[
205
+ str,
206
+ typer.Option(
207
+ '--send-via',
208
+ help='For oauth_auth_client_credentials_flow, how the token will be sent to the server. Defaults to using `header`'
209
+ )
210
+ ] = None,
204
211
  token_url: Annotated[
205
212
  str,
206
213
  typer.Option(
@@ -220,14 +227,14 @@ def set_credentials_connection_command(
220
227
  str,
221
228
  typer.Option(
222
229
  '--grant-type',
223
- help='For oauth_auth_on_behalf_of_flow, the grant type used by the application token server'
230
+ help='For oauth_auth_on_behalf_of_flow and oauth_auth_client_credentials_flow, the grant type used by the application token server'
224
231
  )
225
232
  ] = None,
226
- scopes: Annotated[
227
- List[str],
233
+ scope: Annotated[
234
+ str,
228
235
  typer.Option(
229
- '--scopes',
230
- help='For oauth_auth_code_flow and oauth_auth_client_credentials_flow, the optional scopes used by the application token server'
236
+ '--scope',
237
+ help='For oauth_auth_code_flow and oauth_auth_client_credentials_flow, the optional scopes used by the application token server. Should be in the form of a space seperated string.'
231
238
  )
232
239
  ] = None,
233
240
  entries: Annotated[
@@ -247,10 +254,11 @@ def set_credentials_connection_command(
247
254
  api_key=api_key,
248
255
  client_id=client_id,
249
256
  client_secret=client_secret,
257
+ send_via=send_via,
250
258
  token_url=token_url,
251
259
  auth_url=auth_url,
252
260
  grant_type=grant_type,
253
- scopes=scopes,
261
+ scope=scope,
254
262
  entries=entries
255
263
  )
256
264
 
@@ -160,13 +160,13 @@ def _validate_connection_params(type: ConnectionType, **args) -> None:
160
160
  f"Missing flags --token-url is required for type {type}"
161
161
  )
162
162
 
163
-
164
163
  if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
165
164
  args.get('grant_type') is None
166
165
  ):
167
166
  raise typer.BadParameter(
168
167
  f"Missing flags --grant-type is required for type {type}"
169
168
  )
169
+
170
170
 
171
171
  def _parse_entry(entry: str) -> dict[str,str]:
172
172
  split_entry = entry.split('=')
@@ -197,15 +197,13 @@ def _get_credentials(type: ConnectionType, **kwargs):
197
197
  client_id=kwargs.get("client_id"),
198
198
  client_secret=kwargs.get("client_secret"),
199
199
  token_url=kwargs.get("token_url"),
200
- scopes=kwargs.get("scopes")
200
+ scope=kwargs.get("scope")
201
201
  )
202
202
  case ConnectionType.OAUTH2_CLIENT_CREDS:
203
- return OAuth2ClientCredentials(
204
- client_id=kwargs.get("client_id"),
205
- client_secret=kwargs.get("client_secret"),
206
- token_url=kwargs.get("token_url"),
207
- scopes=kwargs.get("scopes")
208
- )
203
+ # using filtered args as default values will not be set if 'None' is passed, causing validation errors
204
+ keys = ["client_id","client_secret","token_url","grant_type","send_via", "scope"]
205
+ filtered_args = { key_name: kwargs[key_name] for key_name in keys if kwargs.get(key_name) }
206
+ return OAuth2ClientCredentials(**filtered_args)
209
207
  # case ConnectionType.OAUTH2_IMPLICIT:
210
208
  # return OAuth2ImplicitCredentials(
211
209
  # authorization_url=kwargs.get("auth_url"),