ibm-watsonx-orchestrate 1.7.0b1__py3-none-any.whl → 1.8.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 (30) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -2
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +29 -1
  3. ibm_watsonx_orchestrate/agent_builder/connections/types.py +14 -2
  4. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -2
  5. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +34 -23
  6. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +14 -6
  7. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -8
  8. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  9. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +368 -0
  10. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +170 -0
  11. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -1
  12. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +16 -6
  13. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +20 -2
  14. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +10 -8
  15. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +3 -9
  16. ibm_watsonx_orchestrate/cli/main.py +3 -0
  17. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  18. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +67 -0
  19. ibm_watsonx_orchestrate/client/utils.py +49 -9
  20. ibm_watsonx_orchestrate/docker/compose-lite.yml +19 -2
  21. ibm_watsonx_orchestrate/docker/default.env +10 -6
  22. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +8 -5
  23. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +47 -7
  24. ibm_watsonx_orchestrate/flow_builder/node.py +7 -1
  25. ibm_watsonx_orchestrate/flow_builder/types.py +168 -65
  26. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/METADATA +2 -2
  27. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/RECORD +30 -26
  28. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/WHEEL +0 -0
  29. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/entry_points.txt +0 -0
  30. {ibm_watsonx_orchestrate-1.7.0b1.dist-info → ibm_watsonx_orchestrate-1.8.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -5,8 +5,7 @@
5
5
 
6
6
  pkg_name = "ibm-watsonx-orchestrate"
7
7
 
8
- __version__ = "1.7.0b1"
9
-
8
+ __version__ = "1.8.0b1"
10
9
 
11
10
 
12
11
 
@@ -14,17 +14,39 @@ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
14
14
  from ibm_watsonx_orchestrate.agent_builder.tools.types import JsonSchemaObject
15
15
 
16
16
  # TO-DO: this is just a placeholder. Will update this later to align with backend
17
- DEFAULT_LLM = "watsonx/meta-llama/llama-3-1-70b-instruct"
17
+ DEFAULT_LLM = "watsonx/meta-llama/llama-3-2-90b-vision-instruct"
18
+
19
+ # Handles yaml formatting for multiline strings to improve readability
20
+ def str_presenter(dumper, data):
21
+ if len(data.splitlines()) > 1: # check for multiline string
22
+ data = "\n".join([line.rstrip() for line in data.splitlines()])
23
+ return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
24
+ return dumper.represent_scalar('tag:yaml.org,2002:str', data)
25
+
26
+ yaml.add_representer(str, str_presenter)
27
+ yaml.representer.SafeRepresenter.add_representer(str, str_presenter) # to use with safe_dum
18
28
 
19
29
  class SpecVersion(str, Enum):
20
30
  V1 = "v1"
21
31
 
32
+ def __str__(self):
33
+ return self.value
34
+
35
+ def __repr__(self):
36
+ return repr(self.value)
37
+
22
38
 
23
39
  class AgentKind(str, Enum):
24
40
  NATIVE = "native"
25
41
  EXTERNAL = "external"
26
42
  ASSISTANT = "assistant"
27
43
 
44
+ def __str__(self):
45
+ return self.value
46
+
47
+ def __repr__(self):
48
+ return repr(self.value)
49
+
28
50
  class ExternalAgentAuthScheme(str, Enum):
29
51
  BEARER_TOKEN = 'BEARER_TOKEN'
30
52
  API_KEY = "API_KEY"
@@ -88,6 +110,12 @@ class AgentStyle(str, Enum):
88
110
  REACT = "react"
89
111
  PLANNER = "planner"
90
112
 
113
+ def __str__(self):
114
+ return self.value
115
+
116
+ def __repr__(self):
117
+ return repr(self.value)
118
+
91
119
  class AgentGuideline(BaseModel):
92
120
  model_config = ConfigDict(arbitrary_types_allowed=True)
93
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
@@ -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,7 +4,8 @@ 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.client.utils import is_local_dev, is_ibm_cloud, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT, AUTH_CONFIG_FILE
7
+ from ibm_watsonx_orchestrate.cli.commands.channels.types import RuntimeEnvironmentType
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
 
@@ -22,7 +23,7 @@ class ChannelsWebchatController:
22
23
  return self.native_client
23
24
 
24
25
  def extract_tenant_id_from_crn(self, crn: str) -> str:
25
- is_ibm_cloud_env = is_ibm_cloud()
26
+ is_ibm_cloud_env = is_ibm_cloud_platform()
26
27
  if is_ibm_cloud_env:
27
28
  try:
28
29
  parts = crn.split("a/")[1].split(":")
@@ -77,6 +78,7 @@ class ChannelsWebchatController:
77
78
  agent_environments = agent.get("environments", [])
78
79
 
79
80
  is_local = is_local_dev()
81
+ is_saas = is_saas_env()
80
82
  target_env = env or 'draft'
81
83
 
82
84
  if is_local:
@@ -91,7 +93,7 @@ class ChannelsWebchatController:
91
93
  logger.error(f'This agent does not exist in the {env} environment. You need to deploy it to {env} before you can embed the agent')
92
94
  exit(1)
93
95
 
94
- if target_env == 'draft' and is_local == False:
96
+ if target_env == 'draft' and is_saas == True:
95
97
  logger.error(f'For SAAS, please ensure this agent exists in a Live Environment')
96
98
  exit(1)
97
99
 
@@ -99,7 +101,7 @@ class ChannelsWebchatController:
99
101
 
100
102
  return filtered_environments[0].get("id")
101
103
 
102
- def get_tennent_id(self):
104
+ def get_tenant_id(self):
103
105
  auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
104
106
 
105
107
  cfg = Config()
@@ -154,30 +156,40 @@ class ChannelsWebchatController:
154
156
 
155
157
  def create_webchat_embed_code(self):
156
158
  crn = None
157
- is_ibm_cloud_env = is_ibm_cloud()
158
- is_local = is_local_dev()
159
- if is_ibm_cloud_env is True:
160
- crn = input("Please enter your CRN which can be gotten from the IBM Cloud UI: ")
161
- if crn == "":
162
- logger.error("You must enter your CRN for IBM Cloud instances")
159
+ environment = get_environment()
160
+
161
+ match (environment):
162
+ case RuntimeEnvironmentType.LOCAL:
163
+ tenant_id = self.get_tenant_id_local()
164
+
165
+ case RuntimeEnvironmentType.CPD:
166
+ tenant_id = get_cpd_instance_id_from_url()
167
+
168
+ case RuntimeEnvironmentType.IBM_CLOUD:
169
+ crn = input("Please enter your CRN which can be retrieved from the IBM Cloud UI: ")
170
+ if crn == "":
171
+ logger.error("You must enter your CRN for IBM Cloud instances")
172
+ sys.exit(1)
173
+ is_crn_correct = self.check_crn_is_correct(crn)
174
+ if is_crn_correct == False:
175
+ logger.error("Invalid CRN format provided.")
176
+ sys.exit(1)
177
+ tenant_id = self.extract_tenant_id_from_crn(crn)
178
+
179
+ case RuntimeEnvironmentType.AWS:
180
+ tenant_id = self.get_tenant_id()
181
+
182
+ case _:
183
+ logger.error("Environment not recognized")
163
184
  sys.exit(1)
164
- is_crn_correct = self.check_crn_is_correct(crn)
165
- if is_crn_correct == False:
166
- logger.error("Invalid CRN format provided.")
167
-
168
- if is_ibm_cloud_env and crn is not None:
169
- tenant_id = self.extract_tenant_id_from_crn(crn)
170
- elif is_ibm_cloud_env is False and is_local is False:
171
- tenant_id = self.get_tennent_id()
172
- elif is_local:
173
- tenant_id = self.get_tenant_id_local()
185
+
174
186
  host_url = self.get_host_url()
175
187
  agent_id = self.get_agent_id(self.agent_name)
176
188
  agent_env_id = self.get_environment_id(self.agent_name, self.env)
177
189
 
178
190
  script_path = (
179
191
  "/wxoLoader.js?embed=true"
180
- if is_local
192
+ if environment == "local"
181
193
  else "/wxochat/wxoLoader.js?embed=true"
182
194
  )
183
195
 
@@ -189,9 +201,8 @@ class ChannelsWebchatController:
189
201
  ]
190
202
 
191
203
  # Conditional fields for IBM Cloud
192
- if is_ibm_cloud_env:
204
+ if environment == "ibmcloud":
193
205
  config_lines.append(f'crn: "{crn}"')
194
- if is_ibm_cloud_env:
195
206
  config_lines.append(f'deploymentPlatform: "ibmcloud"')
196
207
 
197
208
  config_lines.append(f"""chatOptions: {{
@@ -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"),
@@ -0,0 +1,65 @@
1
+ import typer
2
+ from typing_extensions import Annotated
3
+ from pathlib import Path
4
+ from ibm_watsonx_orchestrate.cli.commands.copilot.copilot_controller import prompt_tune, create_agent
5
+ from ibm_watsonx_orchestrate.cli.commands.copilot.copilot_server_controller import start_server, stop_server
6
+
7
+ copilot_app = typer.Typer(no_args_is_help=True)
8
+
9
+ # Server Commands
10
+ @copilot_app.command(name="start", help='Start the copilot server')
11
+ def start_server_command(
12
+ user_env_file: str = typer.Option(
13
+ None,
14
+ "--env-file", "-e",
15
+ help="Path to a .env file that overrides default.env. Then environment variables override both."
16
+ )
17
+ ):
18
+ user_env_file_path = Path(user_env_file) if user_env_file else None
19
+
20
+ start_server(user_env_file_path=user_env_file_path)
21
+
22
+ @copilot_app.command(name="stop", help='Stop the copilot server')
23
+ def stop_server_command():
24
+ stop_server()
25
+
26
+ # Functional Commands
27
+ @copilot_app.command(name="prompt-tune", help='Tune the instructions of an Agent using IBM Conversational Prompt Engineering (CPE) to improve agent performance')
28
+ def prompt_tume_command(
29
+ file: Annotated[
30
+ str,
31
+ typer.Option("--file", "-f", help="Path to agent spec file"),
32
+ ] = None,
33
+ output_file: Annotated[
34
+ str,
35
+ typer.Option("--output-file", "-o", help="Optional output file to avoid overwriting existing agent spec"),
36
+ ] = None,
37
+ dry_run_flag: Annotated[
38
+ bool,
39
+ typer.Option("--dry-run", help="Dry run will prevent the tuned content being saved and output the results to console"),
40
+ ] = False,
41
+ llm: Annotated[
42
+ str,
43
+ typer.Option("--llm", help="Select the agent LLM"),
44
+ ] = None,
45
+ samples: Annotated[
46
+ str,
47
+ typer.Option("--samples", "-s", help="Path to utterances sample file (txt file where each line is a utterance, or csv file with a single \"input\" column)"),
48
+ ] = None
49
+ ):
50
+ if file is None:
51
+ # create agent yaml from scratch
52
+ create_agent(
53
+ llm=llm,
54
+ output_file=output_file,
55
+ samples_file=samples,
56
+ dry_run_flag=dry_run_flag
57
+ )
58
+ else:
59
+ # improve existing agent instruction
60
+ prompt_tune(
61
+ agent_spec=file,
62
+ samples_file=samples,
63
+ output_file=output_file,
64
+ dry_run_flag=dry_run_flag,
65
+ )