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.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +12 -0
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -2
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +61 -11
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -2
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +3 -3
- ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +7 -7
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -8
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +111 -36
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +23 -7
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +102 -37
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +20 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +10 -8
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +5 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +2 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +5 -30
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +2 -1
- ibm_watsonx_orchestrate/client/utils.py +22 -20
- ibm_watsonx_orchestrate/docker/compose-lite.yml +12 -5
- ibm_watsonx_orchestrate/docker/default.env +13 -12
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +8 -5
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +47 -7
- ibm_watsonx_orchestrate/flow_builder/node.py +7 -1
- ibm_watsonx_orchestrate/flow_builder/types.py +168 -65
- {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/METADATA +2 -2
- {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/RECORD +32 -32
- {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.8.0b0.dist-info → ibm_watsonx_orchestrate-1.9.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
228
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
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
|
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
|
161
|
+
case RuntimeEnvironmentType.LOCAL:
|
162
162
|
tenant_id = self.get_tenant_id_local()
|
163
163
|
|
164
|
-
case
|
164
|
+
case RuntimeEnvironmentType.CPD:
|
165
165
|
tenant_id = get_cpd_instance_id_from_url()
|
166
166
|
|
167
|
-
case
|
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
|
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
|
-
|
227
|
-
|
233
|
+
scope: Annotated[
|
234
|
+
str,
|
228
235
|
typer.Option(
|
229
|
-
'--
|
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
|
-
|
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
|
-
|
200
|
+
scope=kwargs.get("scope")
|
201
201
|
)
|
202
202
|
case ConnectionType.OAUTH2_CLIENT_CREDS:
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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"),
|