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.
- ibm_watsonx_orchestrate/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
- ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
- ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +26 -9
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +25 -10
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +5 -9
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +10 -9
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +35 -7
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +33 -23
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -4
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +65 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +293 -0
- ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +154 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +33 -19
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +66 -9
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
- ibm_watsonx_orchestrate/cli/config.py +3 -3
- ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
- ibm_watsonx_orchestrate/cli/main.py +5 -0
- ibm_watsonx_orchestrate/client/base_api_client.py +12 -0
- ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +66 -0
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
- ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
- ibm_watsonx_orchestrate/client/service_instance.py +33 -7
- ibm_watsonx_orchestrate/client/utils.py +48 -9
- ibm_watsonx_orchestrate/docker/compose-lite.yml +16 -4
- ibm_watsonx_orchestrate/docker/default.env +25 -15
- ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +3 -1
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
- ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
- ibm_watsonx_orchestrate/flow_builder/flows/flow.py +91 -20
- ibm_watsonx_orchestrate/flow_builder/node.py +12 -1
- ibm_watsonx_orchestrate/flow_builder/types.py +169 -16
- ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
- ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/METADATA +5 -5
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/RECORD +56 -52
- ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.8.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -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
|
17
|
-
|
17
|
+
raise BadRequest('file must end in .json, .yaml, or .yml')
|
18
18
|
if not content.get("spec_version"):
|
19
|
-
raise
|
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
|
17
|
+
raise BadRequest('file must end in .json, .yaml, or .yml')
|
17
18
|
|
18
19
|
if not content.get("spec_version"):
|
19
|
-
raise
|
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
|
17
|
+
raise BadRequest('file must end in .json, .yaml, or .yml')
|
17
18
|
|
18
19
|
if not content.get("spec_version"):
|
19
|
-
raise
|
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-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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")
|
@@ -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
|
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:
|
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:
|
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) ->
|
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
|
118
|
+
raise BadRequest(message)
|
105
119
|
|
106
|
-
auth_types = {e.value for e in
|
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=
|
119
|
-
message = f"The requested type '{
|
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
|
136
|
+
raise BadRequest(message)
|
122
137
|
|
123
|
-
return _get_credentials_model(connection_type=
|
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
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
125
|
-
f"
|
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
|
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
|
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
|
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
|
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
|
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
|
280
|
+
raise BadRequest(f"Unrecognized path or uri {openapi_uri}")
|
280
281
|
|
281
282
|
return openapi_contents
|
282
283
|
|