ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.8.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 (57) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
  3. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
  4. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
  5. ibm_watsonx_orchestrate/agent_builder/agents/types.py +26 -9
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +1 -0
  7. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +25 -10
  8. ibm_watsonx_orchestrate/agent_builder/connections/types.py +5 -9
  9. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
  10. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
  11. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
  13. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
  14. ibm_watsonx_orchestrate/agent_builder/tools/types.py +10 -9
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +35 -7
  17. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +33 -23
  18. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  19. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -4
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
  21. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +293 -0
  22. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +154 -0
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +2 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
  28. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +33 -19
  29. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  30. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +66 -9
  31. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
  33. ibm_watsonx_orchestrate/cli/config.py +3 -3
  34. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  35. ibm_watsonx_orchestrate/cli/main.py +5 -0
  36. ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
  37. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +66 -0
  38. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  39. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  40. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  41. ibm_watsonx_orchestrate/client/utils.py +48 -9
  42. ibm_watsonx_orchestrate/docker/compose-lite.yml +16 -4
  43. ibm_watsonx_orchestrate/docker/default.env +25 -15
  44. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +3 -1
  45. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  46. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  47. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +91 -20
  48. ibm_watsonx_orchestrate/flow_builder/node.py +12 -1
  49. ibm_watsonx_orchestrate/flow_builder/types.py +169 -16
  50. ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
  51. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  52. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/METADATA +5 -5
  53. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/RECORD +56 -52
  54. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  55. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/WHEEL +0 -0
  56. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/entry_points.txt +0 -0
  57. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.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.7.0a0"
8
+ __version__ = "1.8.0b0"
9
9
 
10
10
 
11
11
 
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
3
  from .types import AgentSpec
4
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
4
5
 
5
6
 
6
7
  class Agent(AgentSpec):
@@ -13,10 +14,9 @@ class Agent(AgentSpec):
13
14
  elif file.endswith('.json'):
14
15
  content = json.load(f)
15
16
  else:
16
- raise ValueError('file must end in .json, .yaml, or .yml')
17
-
17
+ raise BadRequest('file must end in .json, .yaml, or .yml')
18
18
  if not content.get("spec_version"):
19
- raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
19
+ raise BadRequest(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
20
  agent = Agent.model_validate(content)
21
21
 
22
22
  return agent
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
3
  from .types import AssistantAgentSpec
4
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
4
5
 
5
6
 
6
7
  class AssistantAgent(AssistantAgentSpec):
@@ -13,10 +14,10 @@ class AssistantAgent(AssistantAgentSpec):
13
14
  elif file.endswith('.json'):
14
15
  content = json.load(f)
15
16
  else:
16
- raise ValueError('file must end in .json, .yaml, or .yml')
17
+ raise BadRequest('file must end in .json, .yaml, or .yml')
17
18
 
18
19
  if not content.get("spec_version"):
19
- raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
+ raise BadRequest(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
21
  agent = AssistantAgent.model_validate(content)
21
22
 
22
23
  return agent
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
3
  from .types import ExternalAgentSpec
4
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
4
5
 
5
6
 
6
7
  class ExternalAgent(ExternalAgentSpec):
@@ -13,10 +14,10 @@ class ExternalAgent(ExternalAgentSpec):
13
14
  elif file.endswith('.json'):
14
15
  content = json.load(f)
15
16
  else:
16
- raise ValueError('file must end in .json, .yaml, or .yml')
17
+ raise BadRequest('file must end in .json, .yaml, or .yml')
17
18
 
18
19
  if not content.get("spec_version"):
19
- raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
+ raise BadRequest(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
21
  agent = ExternalAgent.model_validate(content)
21
22
 
22
23
  return agent
@@ -9,11 +9,22 @@ from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import
9
9
  from ibm_watsonx_orchestrate.agent_builder.agents.webchat_customizations import StarterPrompts, WelcomeContent
10
10
  from pydantic import Field, AliasChoices
11
11
  from typing import Annotated
12
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
12
13
 
13
14
  from ibm_watsonx_orchestrate.agent_builder.tools.types import JsonSchemaObject
14
15
 
15
16
  # TO-DO: this is just a placeholder. Will update this later to align with backend
16
- 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
17
28
 
18
29
  class SpecVersion(str, Enum):
19
30
  V1 = "v1"
@@ -24,6 +35,12 @@ class AgentKind(str, Enum):
24
35
  EXTERNAL = "external"
25
36
  ASSISTANT = "assistant"
26
37
 
38
+ def __str__(self):
39
+ return self.value
40
+
41
+ def __repr__(self):
42
+ return repr(self.value)
43
+
27
44
  class ExternalAgentAuthScheme(str, Enum):
28
45
  BEARER_TOKEN = 'BEARER_TOKEN'
29
46
  API_KEY = "API_KEY"
@@ -63,7 +80,7 @@ class BaseAgentSpec(BaseModel):
63
80
  elif file.endswith('.json'):
64
81
  json.dump(dumped, f, indent=2)
65
82
  else:
66
- raise ValueError('file must end in .json, .yaml, or .yml')
83
+ raise BadRequest('file must end in .json, .yaml, or .yml')
67
84
 
68
85
  def dumps_spec(self) -> str:
69
86
  dumped = self.model_dump(mode='json', exclude_none=True)
@@ -136,7 +153,7 @@ class AgentSpec(BaseAgentSpec):
136
153
  @model_validator(mode="after")
137
154
  def validate_kind(self):
138
155
  if self.kind != AgentKind.NATIVE:
139
- raise ValueError(f"The specified kind '{self.kind}' cannot be used to create a native agent.")
156
+ raise BadRequest(f"The specified kind '{self.kind}' cannot be used to create a native agent.")
140
157
  return self
141
158
 
142
159
  def validate_agent_fields(values: dict) -> dict:
@@ -144,13 +161,13 @@ def validate_agent_fields(values: dict) -> dict:
144
161
  for field in ["id", "name", "kind", "description", "collaborators", "tools", "knowledge_base"]:
145
162
  value = values.get(field)
146
163
  if value and not str(value).strip():
147
- raise ValueError(f"{field} cannot be empty or just whitespace")
164
+ raise BadRequest(f"{field} cannot be empty or just whitespace")
148
165
 
149
166
  name = values.get("name")
150
167
  collaborators = values.get("collaborators", []) if values.get("collaborators", []) else []
151
168
  for collaborator in collaborators:
152
169
  if collaborator == name:
153
- raise ValueError(f"Circular reference detected. The agent '{name}' cannot contain itself as a collaborator")
170
+ raise BadRequest(f"Circular reference detected. The agent '{name}' cannot contain itself as a collaborator")
154
171
 
155
172
  if values.get("style") == AgentStyle.PLANNER:
156
173
  if values.get("custom_join_tool") and values.get("structured_output"):
@@ -197,7 +214,7 @@ class ExternalAgentSpec(BaseAgentSpec):
197
214
  @model_validator(mode="after")
198
215
  def validate_kind_for_external(self):
199
216
  if self.kind != AgentKind.EXTERNAL:
200
- raise ValueError(f"The specified kind '{self.kind}' cannot be used to create an external agent.")
217
+ raise BadRequest(f"The specified kind '{self.kind}' cannot be used to create an external agent.")
201
218
  return self
202
219
 
203
220
  def validate_external_agent_fields(values: dict) -> dict:
@@ -205,7 +222,7 @@ def validate_external_agent_fields(values: dict) -> dict:
205
222
  for field in ["name", "kind", "description", "title", "tags", "api_url", "chat_params", "nickname", "app_id"]:
206
223
  value = values.get(field)
207
224
  if value and not str(value).strip():
208
- raise ValueError(f"{field} cannot be empty or just whitespace")
225
+ raise BadRequest(f"{field} cannot be empty or just whitespace")
209
226
 
210
227
  context_variables = values.get("context_variables")
211
228
  if context_variables is not None:
@@ -250,7 +267,7 @@ class AssistantAgentSpec(BaseAgentSpec):
250
267
  @model_validator(mode="after")
251
268
  def validate_kind_for_external(self):
252
269
  if self.kind != AgentKind.ASSISTANT:
253
- raise ValueError(f"The specified kind '{self.kind}' cannot be used to create an assistant agent.")
270
+ raise BadRequest(f"The specified kind '{self.kind}' cannot be used to create an assistant agent.")
254
271
  return self
255
272
 
256
273
  def validate_assistant_agent_fields(values: dict) -> dict:
@@ -258,7 +275,7 @@ def validate_assistant_agent_fields(values: dict) -> dict:
258
275
  for field in ["name", "kind", "description", "title", "tags", "nickname", "app_id"]:
259
276
  value = values.get(field)
260
277
  if value and not str(value).strip():
261
- raise ValueError(f"{field} cannot be empty or just whitespace")
278
+ raise BadRequest(f"{field} cannot be empty or just whitespace")
262
279
 
263
280
  # Validate context_variables if provided
264
281
  context_variables = values.get("context_variables")
@@ -7,6 +7,7 @@ from pydantic import BaseModel, model_validator
7
7
  class PromptState(str,Enum):
8
8
  ACTIVE = "active"
9
9
  INACTIVE = "inactive"
10
+ MISSING = ""
10
11
 
11
12
 
12
13
  class AgentPrompt(BaseModel):
@@ -8,11 +8,13 @@ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
8
8
  OAuth2TokenCredentials,
9
9
  KeyValueConnectionCredentials,
10
10
  ConnectionType,
11
+ ConnectionSecurityScheme,
11
12
  CREDENTIALS,
12
13
  CONNECTION_TYPE_CREDENTIAL_MAPPING
13
14
  )
14
15
 
15
16
  from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
17
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
16
18
 
17
19
  logger = logging.getLogger(__name__)
18
20
 
@@ -26,6 +28,18 @@ connection_type_requirements_mapping = {
26
28
  KeyValueConnectionCredentials: None
27
29
  }
28
30
 
31
+ connection_type_security_schema_map = {
32
+ ConnectionType.API_KEY_AUTH: ConnectionSecurityScheme.API_KEY_AUTH,
33
+ ConnectionType.BASIC_AUTH: ConnectionSecurityScheme.BASIC_AUTH,
34
+ ConnectionType.BEARER_TOKEN: ConnectionSecurityScheme.BEARER_TOKEN,
35
+ ConnectionType.KEY_VALUE: ConnectionSecurityScheme.KEY_VALUE,
36
+ ConnectionType.OAUTH2_AUTH_CODE: ConnectionSecurityScheme.OAUTH2,
37
+ ConnectionType.OAUTH2_CLIENT_CREDS: ConnectionSecurityScheme.OAUTH2,
38
+ ConnectionType.OAUTH_ON_BEHALF_OF_FLOW: ConnectionSecurityScheme.OAUTH2,
39
+ # ConnectionType.OAUTH2_IMPLICIT: ConnectionSecurityScheme.OAUTH2,
40
+ # ConnectionType.OAUTH2_PASSWORD: ConnectionSecurityScheme.OAUTH2
41
+ }
42
+
29
43
  def _clean_env_vars(vars: dict[str:str], requirements: List[str], app_id: str) -> dict[str,str]:
30
44
  base_prefix = _PREFIX_TEMPLATE.format(app_id=app_id)
31
45
 
@@ -48,7 +62,7 @@ def _clean_env_vars(vars: dict[str:str], requirements: List[str], app_id: str) -
48
62
  missing_requirements_str = ", ".join(missing_requirements)
49
63
  message = f"Missing requirement environment variables '{missing_requirements_str}' for connection '{app_id}'"
50
64
  logger.error(message)
51
- raise ValueError(message)
65
+ raise BadRequest(message)
52
66
 
53
67
  return required_env_vars
54
68
 
@@ -75,10 +89,10 @@ def _build_credentials_model(credentials_type: type[CREDENTIALS], vars: dict[str
75
89
  )
76
90
 
77
91
 
78
- def _validate_schema_type(requested_type: ConnectionType, expected_type: ConnectionType) -> bool:
92
+ def _validate_schema_type(requested_type: ConnectionSecurityScheme, expected_type: ConnectionSecurityScheme) -> bool:
79
93
  return expected_type == requested_type
80
94
 
81
- def _get_credentials_model(connection_type: ConnectionType, app_id: str) -> type[CREDENTIALS]:
95
+ def _get_credentials_model(connection_type: ConnectionSecurityScheme, app_id: str) -> type[CREDENTIALS]:
82
96
  base_prefix = _PREFIX_TEMPLATE.format(app_id=app_id)
83
97
  variables = {}
84
98
  for key, value in os.environ.items():
@@ -93,7 +107,7 @@ def _get_credentials_model(connection_type: ConnectionType, app_id: str) -> type
93
107
 
94
108
  return _build_credentials_model(credentials_type=credentials_type, vars=variables, base_prefix=base_prefix)
95
109
 
96
- def get_connection_type(app_id: str) -> ConnectionType:
110
+ def get_connection_type(app_id: str) -> ConnectionSecurityScheme:
97
111
  sanitized_app_id = sanatize_app_id(app_id=app_id)
98
112
  expected_schema_key = f"WXO_SECURITY_SCHEMA_{sanitized_app_id}"
99
113
  expected_schema = os.environ.get(expected_schema_key)
@@ -101,9 +115,9 @@ def get_connection_type(app_id: str) -> ConnectionType:
101
115
  if not expected_schema:
102
116
  message = f"No credentials found for connections '{app_id}'"
103
117
  logger.error(message)
104
- raise ValueError(message)
118
+ raise BadRequest(message)
105
119
 
106
- auth_types = {e.value for e in ConnectionType}
120
+ auth_types = {e.value for e in ConnectionSecurityScheme}
107
121
  if expected_schema not in auth_types:
108
122
  message = f"The expected type '{expected_schema}' cannot be resolved into a valid connection auth type ({', '.join(list(auth_types))})"
109
123
  logger.error(message)
@@ -114,10 +128,11 @@ def get_connection_type(app_id: str) -> ConnectionType:
114
128
  def get_application_connection_credentials(type: ConnectionType, app_id: str) -> CREDENTIALS:
115
129
  sanitized_app_id = sanatize_app_id(app_id=app_id)
116
130
  expected_schema = get_connection_type(app_id=app_id)
131
+ requested_schema = connection_type_security_schema_map.get(type)
117
132
 
118
- if not _validate_schema_type(requested_type=type, expected_type=expected_schema):
119
- message = f"The requested type '{type.__name__}' does not match the type '{expected_schema}' for the connection '{app_id}'"
133
+ if not _validate_schema_type(requested_type=requested_schema, expected_type=expected_schema):
134
+ message = f"The requested type '{requested_schema}' does not match the type '{expected_schema}' for the connection '{app_id}'"
120
135
  logger.error(message)
121
- raise ValueError(message)
136
+ raise BadRequest(message)
122
137
 
123
- return _get_credentials_model(connection_type=type, app_id=sanitized_app_id)
138
+ return _get_credentials_model(connection_type=requested_schema, app_id=sanitized_app_id)
@@ -244,15 +244,11 @@ CONNECTION_KIND_OAUTH_TYPE_MAPPING = {
244
244
  }
245
245
 
246
246
  CONNECTION_TYPE_CREDENTIAL_MAPPING = {
247
- ConnectionType.BASIC_AUTH: BasicAuthCredentials,
248
- ConnectionType.BEARER_TOKEN: BearerTokenAuthCredentials,
249
- ConnectionType.API_KEY_AUTH: APIKeyAuthCredentials,
250
- ConnectionType.OAUTH2_AUTH_CODE: OAuth2TokenCredentials,
251
- # ConnectionType.OAUTH2_IMPLICIT: OAuth2TokenCredentials,
252
- # ConnectionType.OAUTH2_PASSWORD: OAuth2TokenCredentials,
253
- ConnectionType.OAUTH2_CLIENT_CREDS: OAuth2TokenCredentials,
254
- ConnectionType.OAUTH_ON_BEHALF_OF_FLOW: OAuth2TokenCredentials,
255
- ConnectionType.KEY_VALUE: KeyValueConnectionCredentials,
247
+ ConnectionSecurityScheme.BASIC_AUTH: BasicAuthCredentials,
248
+ ConnectionSecurityScheme.BEARER_TOKEN: BearerTokenAuthCredentials,
249
+ ConnectionSecurityScheme.API_KEY_AUTH: APIKeyAuthCredentials,
250
+ ConnectionSecurityScheme.OAUTH2: OAuth2TokenCredentials,
251
+ ConnectionSecurityScheme.KEY_VALUE: KeyValueConnectionCredentials,
256
252
  }
257
253
 
258
254
  class IdentityProviderCredentials(BaseModel):
@@ -1,7 +1,6 @@
1
1
  import json
2
2
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
- from .types import KnowledgeBaseSpec, PatchKnowledgeBase, KnowledgeBaseKind
4
-
3
+ from .types import KnowledgeBaseSpec
5
4
 
6
5
  class KnowledgeBaseCreateRequest(KnowledgeBaseSpec):
7
6
 
@@ -21,23 +20,3 @@ class KnowledgeBaseCreateRequest(KnowledgeBaseSpec):
21
20
  knowledge_base = KnowledgeBaseSpec.model_validate(content)
22
21
 
23
22
  return knowledge_base
24
-
25
-
26
- class KnowledgeBaseUpdateRequest(PatchKnowledgeBase):
27
-
28
- @staticmethod
29
- def from_spec(file: str) -> 'PatchKnowledgeBase':
30
- with open(file, 'r') as f:
31
- if file.endswith('.yaml') or file.endswith('.yml'):
32
- content = yaml_safe_load(f)
33
- elif file.endswith('.json'):
34
- content = json.load(f)
35
- else:
36
- raise ValueError('file must end in .json, .yaml, or .yml')
37
-
38
- if not content.get("spec_version"):
39
- raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
40
-
41
- patch = PatchKnowledgeBase.model_validate(content)
42
-
43
- return patch
@@ -3,7 +3,7 @@ from datetime import datetime
3
3
  from uuid import UUID
4
4
  from enum import Enum
5
5
 
6
- from pydantic import BaseModel, model_validator
6
+ from pydantic import BaseModel
7
7
 
8
8
  class SpecVersion(str, Enum):
9
9
  V1 = "v1"
@@ -219,22 +219,6 @@ class KnowledgeBaseBuiltInVectorIndexConfig(BaseModel):
219
219
  chunk_overlap: Optional[int] = None
220
220
  limit: Optional[int] = None
221
221
 
222
- class PatchKnowledgeBase(BaseModel):
223
- """request payload schema"""
224
- description: Optional[str] = None
225
- documents: list[str] = None
226
- conversational_search_tool: Optional[ConversationalSearchConfig] = None
227
- prioritize_built_in_index: Optional[bool] = None
228
- representation: Optional[KnowledgeBaseRepresentation] = None
229
-
230
- @model_validator(mode="after")
231
- def validate_fields(self):
232
- if self.documents and self.conversational_search_tool and self.conversational_search_tool.index_config:
233
- raise ValueError("Must not provide both \"documents\" or \"conversational_search_tool.index_config\"")
234
- if self.conversational_search_tool and self.conversational_search_tool.index_config and len(self.conversational_search_tool.index_config) != 1:
235
- raise ValueError(f"Must provide exactly one conversational_search_tool.index_config. Provided {len(self.conversational_search_tool.index_config)}.")
236
- return self
237
-
238
222
  class KnowledgeBaseSpec(BaseModel):
239
223
  """Schema for a complete knowledge-base."""
240
224
  spec_version: SpecVersion = None
@@ -4,6 +4,7 @@ import yaml
4
4
 
5
5
  from .types import ToolSpec
6
6
 
7
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
7
8
 
8
9
  class BaseTool:
9
10
  __tool_spec__: ToolSpec
@@ -22,7 +23,7 @@ class BaseTool:
22
23
  elif file.endswith('.json'):
23
24
  json.dump(dumped, f, indent=2)
24
25
  else:
25
- raise ValueError('file must end in .json, .yaml, or .yml')
26
+ raise BadRequest('file must end in .json, .yaml, or .yml')
26
27
 
27
28
  def dumps_spec(self) -> str:
28
29
  dumped = self.__tool_spec__.model_dump(mode='json', exclude_unset=True, exclude_none=True, by_alias=True)
@@ -3,6 +3,7 @@ import json
3
3
  import os.path
4
4
  import logging
5
5
  from typing import Dict, Any, List
6
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
6
7
 
7
8
  import yaml
8
9
  import yaml.constructor
@@ -40,7 +41,7 @@ class OpenAPITool(BaseTool):
40
41
  BaseTool.__init__(self, spec=spec)
41
42
 
42
43
  if self.__tool_spec__.binding.openapi is None:
43
- raise ValueError('Missing openapi binding')
44
+ raise BadRequest('Missing openapi binding')
44
45
 
45
46
  async def __call__(self, **kwargs):
46
47
  raise RuntimeError('OpenAPI Tools are only available when deployed onto watson orchestrate or the watson '
@@ -54,10 +55,10 @@ class OpenAPITool(BaseTool):
54
55
  elif file.endswith('.json'):
55
56
  spec = ToolSpec.model_validate(json.load(f))
56
57
  else:
57
- raise ValueError('file must end in .json, .yaml, or .yml')
58
+ raise BadRequest('file must end in .json, .yaml, or .yml')
58
59
 
59
60
  if spec.binding.openapi is None or spec.binding.openapi is None:
60
- raise ValueError('failed to load python tool as the tool had no openapi binding')
61
+ raise BadRequest('failed to load python tool as the tool had no openapi binding')
61
62
 
62
63
  return OpenAPITool(spec=spec)
63
64
 
@@ -108,11 +109,11 @@ def create_openapi_json_tool(
108
109
  paths = openapi_contents.get('paths', {})
109
110
  route = paths.get(http_path)
110
111
  if route is None:
111
- raise ValueError(f"Path {http_path} not found in paths. Available endpoints are: {list(paths.keys())}")
112
+ raise BadRequest(f"Path {http_path} not found in paths. Available endpoints are: {list(paths.keys())}")
112
113
 
113
114
  route_spec = route.get(http_method.lower(), route.get(http_method.upper()))
114
115
  if route_spec is None:
115
- raise ValueError(
116
+ raise BadRequest(
116
117
  f"Path {http_path} did not have an http_method {http_method}. Available methods are {list(route.keys())}")
117
118
 
118
119
  operation_id = re.sub( r'(\W|_)+', '_', route_spec.get('operationId') ) \
@@ -121,12 +122,12 @@ def create_openapi_json_tool(
121
122
  spec_name = name or operation_id
122
123
  spec_permission = permission or _action_to_perm(route_spec.get('x-ibm-operation', {}).get('action'))
123
124
  if spec_name is None:
124
- raise ValueError(
125
- f"No name provided for tool. {http_method}: {http_path} did not specify an operationId, and no name was provided")
125
+ raise BadRequest(
126
+ f"Failed to import tool from endpoint {http_method}: {http_path} as no operationId was provided. An operationId must be provided to generate the name of the tool.")
126
127
 
127
128
  spec_description = description or route_spec.get('description')
128
129
  if spec_description is None:
129
- raise ValueError(
130
+ raise BadRequest(
130
131
  f"No description provided for tool. {http_method}: {http_path} did not specify a description field, and no description was provided")
131
132
 
132
133
  spec = ToolSpec(
@@ -199,7 +200,7 @@ def create_openapi_json_tool(
199
200
  for needed_security in route_spec.get('security', []) + openapi_spec.get('security', []):
200
201
  name = next(iter(needed_security.keys()), None)
201
202
  if name is None or name not in security_schemes_map:
202
- raise ValueError(f"Invalid openapi spec, {HTTP_METHOD} {http_path} asks for a security scheme of {name}, "
203
+ raise BadRequest(f"Invalid openapi spec, {HTTP_METHOD} {http_path} asks for a security scheme of {name}, "
203
204
  f"but no such security scheme was configured in the .security section of the spec")
204
205
 
205
206
  security.append(security_schemes_map[name])
@@ -260,23 +261,23 @@ async def _get_openapi_spec_from_uri(openapi_uri: str) -> Dict[str, Any]:
260
261
  elif openapi_uri.endswith('.yaml') or openapi_uri.endswith('.yml'):
261
262
  openapi_contents = yaml_safe_load(fp)
262
263
  else:
263
- raise ValueError(
264
+ raise BadRequest(
264
265
  f"Unexpected file extension for file {openapi_uri}, expected one of [.json, .yaml, .yml]")
265
266
  elif openapi_uri.endswith('.json'):
266
267
  async with httpx.AsyncClient() as client:
267
268
  r = await client.get(openapi_uri)
268
269
  if r.status_code != 200:
269
- raise ValueError(f"Failed to fetch an openapi spec from {openapi_uri}, status code: {r.status_code}")
270
+ raise BadRequest(f"Failed to fetch an openapi spec from {openapi_uri}, status code: {r.status_code}")
270
271
  openapi_contents = r.json()
271
272
  elif openapi_uri.endswith('.yaml'):
272
273
  async with httpx.AsyncClient() as client:
273
274
  r = await client.get(openapi_uri)
274
275
  if r.status_code != 200:
275
- raise ValueError(f"Failed to fetch an openapi spec from {openapi_uri}, status code: {r.status_code}")
276
+ raise BadRequest(f"Failed to fetch an openapi spec from {openapi_uri}, status code: {r.status_code}")
276
277
  openapi_contents = yaml_safe_load(r.text)
277
278
 
278
279
  if openapi_contents is None:
279
- raise ValueError(f"Unrecognized path or uri {openapi_uri}")
280
+ raise BadRequest(f"Unrecognized path or uri {openapi_uri}")
280
281
 
281
282
  return openapi_contents
282
283