ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.6.0__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/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
- ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
- ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
- ibm_watsonx_orchestrate/cli/config.py +2 -0
- ibm_watsonx_orchestrate/cli/main.py +6 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
- ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/credentials.py +4 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
- ibm_watsonx_orchestrate/client/service_instance.py +42 -1
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +37 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
- ibm_watsonx_orchestrate/docker/default.env +40 -15
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
- ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,4 +2,4 @@ from .agent import Agent, AgentSpec
|
|
2
2
|
from .external_agent import ExternalAgent, ExternalAgentSpec
|
3
3
|
from .assistant_agent import AssistantAgent, AssistantAgentSpec
|
4
4
|
# from .types import AgentKind, AgentStyle, ExternalAgentConfig, AssistantAgentConfig, SpecVersion
|
5
|
-
from .types import AgentKind, AgentStyle, SpecVersion, ExternalAgentAuthScheme, AgentProvider, AssistantAgentConfig
|
5
|
+
from .types import AgentKind, AgentStyle, SpecVersion, ExternalAgentAuthScheme, AgentProvider, AssistantAgentConfig, AgentGuideline
|
@@ -14,6 +14,7 @@ class Agent(AgentSpec):
|
|
14
14
|
content = json.load(f)
|
15
15
|
else:
|
16
16
|
raise ValueError('file must end in .json, .yaml, or .yml')
|
17
|
+
|
17
18
|
if not content.get("spec_version"):
|
18
19
|
raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
|
19
20
|
agent = Agent.model_validate(content)
|
@@ -4,8 +4,9 @@ from enum import Enum
|
|
4
4
|
from typing import List, Optional, Dict
|
5
5
|
from pydantic import BaseModel, model_validator, ConfigDict
|
6
6
|
from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool, PythonTool
|
7
|
-
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import KnowledgeBaseSpec
|
7
|
+
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import KnowledgeBaseSpec, KnowledgeBaseBuiltInVectorIndexConfig, HAPFiltering, HAPFilteringConfig, CitationsConfig, ConfidenceThresholds, QueryRewriteConfig, GenerationConfiguration
|
8
8
|
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import KnowledgeBase
|
9
|
+
from ibm_watsonx_orchestrate.agent_builder.agents.webchat_customizations import StarterPrompts, WelcomeContent
|
9
10
|
from pydantic import Field, AliasChoices
|
10
11
|
from typing import Annotated
|
11
12
|
|
@@ -32,7 +33,8 @@ class AgentProvider(str, Enum):
|
|
32
33
|
WXAI = "wx.ai"
|
33
34
|
EXT_CHAT = "external_chat"
|
34
35
|
SALESFORCE = "salesforce"
|
35
|
-
WATSONX = "watsonx"
|
36
|
+
WATSONX = "watsonx"
|
37
|
+
A2A = 'external_chat/A2A/0.2.1' #provider type returned from an assistant agent
|
36
38
|
|
37
39
|
|
38
40
|
class AssistantAgentAuthType(str, Enum):
|
@@ -50,6 +52,8 @@ class BaseAgentSpec(BaseModel):
|
|
50
52
|
name: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
|
51
53
|
display_name: Annotated[Optional[str], Field(json_schema_extra={"min_length_str":1})] = None
|
52
54
|
description: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
|
55
|
+
context_access_enabled: bool = True
|
56
|
+
context_variables: Optional[List[str]] = []
|
53
57
|
|
54
58
|
def dump_spec(self, file: str) -> None:
|
55
59
|
dumped = self.model_dump(mode='json', exclude_unset=True, exclude_none=True)
|
@@ -69,11 +73,34 @@ class BaseAgentSpec(BaseModel):
|
|
69
73
|
# NATIVE AGENT TYPES
|
70
74
|
# ===============================
|
71
75
|
|
76
|
+
class ChatWithDocsConfig(BaseModel):
|
77
|
+
enabled: Optional[bool] = None
|
78
|
+
vector_index: Optional[KnowledgeBaseBuiltInVectorIndexConfig] = None
|
79
|
+
generation: Optional[GenerationConfiguration] = None
|
80
|
+
query_rewrite: Optional[QueryRewriteConfig] = None
|
81
|
+
confidence_thresholds: Optional[ConfidenceThresholds] =None
|
82
|
+
citations: Optional[CitationsConfig] = None
|
83
|
+
hap_filtering: Optional[HAPFiltering] = None
|
84
|
+
|
72
85
|
class AgentStyle(str, Enum):
|
73
86
|
DEFAULT = "default"
|
74
87
|
REACT = "react"
|
75
88
|
PLANNER = "planner"
|
76
89
|
|
90
|
+
class AgentGuideline(BaseModel):
|
91
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
92
|
+
|
93
|
+
display_name: Optional[str] = None
|
94
|
+
condition: str
|
95
|
+
action: str
|
96
|
+
tool: Optional[BaseTool] | Optional[str] = None
|
97
|
+
|
98
|
+
def __init__(self, *args, **kwargs):
|
99
|
+
if "tool" in kwargs and kwargs["tool"]:
|
100
|
+
kwargs["tool"] = kwargs['tool'].__tool_spec__.name if isinstance(kwargs['tool'], BaseTool) else kwargs["tool"]
|
101
|
+
|
102
|
+
super().__init__(*args, **kwargs)
|
103
|
+
|
77
104
|
class AgentSpec(BaseAgentSpec):
|
78
105
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
79
106
|
|
@@ -83,10 +110,14 @@ class AgentSpec(BaseAgentSpec):
|
|
83
110
|
custom_join_tool: str | PythonTool | None = None
|
84
111
|
structured_output: Optional[JsonSchemaObject] = None
|
85
112
|
instructions: Annotated[Optional[str], Field(json_schema_extra={"min_length_str":1})] = None
|
113
|
+
guidelines: Optional[List[AgentGuideline]] = None
|
86
114
|
collaborators: Optional[List[str]] | Optional[List['BaseAgentSpec']] = []
|
87
115
|
tools: Optional[List[str]] | Optional[List['BaseTool']] = []
|
88
116
|
hidden: bool = False
|
89
117
|
knowledge_base: Optional[List[str]] | Optional[List['KnowledgeBaseSpec']] = []
|
118
|
+
chat_with_docs: Optional[ChatWithDocsConfig] = None
|
119
|
+
starter_prompts: Optional[StarterPrompts] = None
|
120
|
+
welcome_content: Optional[WelcomeContent] = None
|
90
121
|
|
91
122
|
|
92
123
|
def __init__(self, *args, **kwargs):
|
@@ -122,11 +153,17 @@ def validate_agent_fields(values: dict) -> dict:
|
|
122
153
|
raise ValueError(f"Circular reference detected. The agent '{name}' cannot contain itself as a collaborator")
|
123
154
|
|
124
155
|
if values.get("style") == AgentStyle.PLANNER:
|
125
|
-
if not values.get("custom_join_tool") and not values.get("structured_output"):
|
126
|
-
raise ValueError("Either 'custom_join_tool' or 'structured_output' must be provided for planner style agents.")
|
127
156
|
if values.get("custom_join_tool") and values.get("structured_output"):
|
128
157
|
raise ValueError("Only one of 'custom_join_tool' or 'structured_output' can be provided for planner style agents.")
|
129
158
|
|
159
|
+
context_variables = values.get("context_variables")
|
160
|
+
if context_variables is not None:
|
161
|
+
if not isinstance(context_variables, list):
|
162
|
+
raise ValueError("context_variables must be a list")
|
163
|
+
for var in context_variables:
|
164
|
+
if not isinstance(var, str) or not var.strip():
|
165
|
+
raise ValueError("All context_variables must be non-empty strings")
|
166
|
+
|
130
167
|
return values
|
131
168
|
|
132
169
|
# ===============================
|
@@ -170,6 +207,14 @@ def validate_external_agent_fields(values: dict) -> dict:
|
|
170
207
|
if value and not str(value).strip():
|
171
208
|
raise ValueError(f"{field} cannot be empty or just whitespace")
|
172
209
|
|
210
|
+
context_variables = values.get("context_variables")
|
211
|
+
if context_variables is not None:
|
212
|
+
if not isinstance(context_variables, list):
|
213
|
+
raise ValueError("context_variables must be a list")
|
214
|
+
for var in context_variables:
|
215
|
+
if not isinstance(var, str) or not var.strip():
|
216
|
+
raise ValueError("All context_variables must be non-empty strings")
|
217
|
+
|
173
218
|
return values
|
174
219
|
|
175
220
|
# # ===============================
|
@@ -215,4 +260,13 @@ def validate_assistant_agent_fields(values: dict) -> dict:
|
|
215
260
|
if value and not str(value).strip():
|
216
261
|
raise ValueError(f"{field} cannot be empty or just whitespace")
|
217
262
|
|
263
|
+
# Validate context_variables if provided
|
264
|
+
context_variables = values.get("context_variables")
|
265
|
+
if context_variables is not None:
|
266
|
+
if not isinstance(context_variables, list):
|
267
|
+
raise ValueError("context_variables must be a list")
|
268
|
+
for var in context_variables:
|
269
|
+
if not isinstance(var, str) or not var.strip():
|
270
|
+
raise ValueError("All context_variables must be non-empty strings")
|
271
|
+
|
218
272
|
return values
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import Optional, Dict, List, Annotated
|
3
|
+
from annotated_types import Len
|
4
|
+
from pydantic import BaseModel, model_validator
|
5
|
+
|
6
|
+
|
7
|
+
class PromptState(str,Enum):
|
8
|
+
ACTIVE = "active"
|
9
|
+
INACTIVE = "inactive"
|
10
|
+
MISSING = ""
|
11
|
+
|
12
|
+
|
13
|
+
class AgentPrompt(BaseModel):
|
14
|
+
id: str
|
15
|
+
title: str
|
16
|
+
subtitle: Optional[str] = None
|
17
|
+
prompt: str
|
18
|
+
state: PromptState = PromptState.ACTIVE
|
19
|
+
|
20
|
+
@model_validator(mode='before')
|
21
|
+
def validate_fields(cls,values):
|
22
|
+
return validate_agent_prompt_fields(values)
|
23
|
+
|
24
|
+
|
25
|
+
def validate_agent_prompt_fields(values: Dict):
|
26
|
+
for field in ['id','title','prompt','state']:
|
27
|
+
value = values.get(field)
|
28
|
+
if value and not str(value).strip():
|
29
|
+
raise ValueError(f"{field} cannot be empty or just whitespace")
|
30
|
+
return values
|
31
|
+
|
32
|
+
class StarterPrompts(BaseModel):
|
33
|
+
is_default_prompts: bool = False
|
34
|
+
prompts: Annotated[List[AgentPrompt], Len(min_length=1)]
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
from typing import Optional,Dict
|
3
|
+
from pydantic import BaseModel, model_validator
|
4
|
+
|
5
|
+
class WelcomeContent(BaseModel):
|
6
|
+
welcome_message: str
|
7
|
+
description: Optional[str] = None
|
8
|
+
is_default_message: bool = False
|
9
|
+
|
10
|
+
@model_validator(mode='before')
|
11
|
+
def validate_fields(cls,values):
|
12
|
+
return validate_welcome_content_fields(values)
|
13
|
+
|
14
|
+
|
15
|
+
def validate_welcome_content_fields(values: Dict):
|
16
|
+
for field in ['welcome_message']:
|
17
|
+
value = values.get(field)
|
18
|
+
if value and not str(value).strip():
|
19
|
+
raise ValueError(f"{field} cannot be empty or just whitespace")
|
20
|
+
return values
|
@@ -15,8 +15,8 @@ from .types import (
|
|
15
15
|
BearerTokenAuthCredentials,
|
16
16
|
APIKeyAuthCredentials,
|
17
17
|
OAuth2TokenCredentials,
|
18
|
-
|
19
|
-
|
18
|
+
OAuth2AuthCodeCredentials,
|
19
|
+
OAuth2ClientCredentials,
|
20
20
|
# OAuth2ImplicitCredentials,
|
21
21
|
# OAuth2PasswordCredentials,
|
22
22
|
OAuthOnBehalfOfCredentials,
|
@@ -6,10 +6,10 @@ class ConnectionKind(str, Enum):
|
|
6
6
|
basic = 'basic'
|
7
7
|
bearer = 'bearer'
|
8
8
|
api_key = 'api_key'
|
9
|
-
|
9
|
+
oauth_auth_code_flow = 'oauth_auth_code_flow'
|
10
10
|
# oauth_auth_implicit_flow = 'oauth_auth_implicit_flow'
|
11
11
|
# oauth_auth_password_flow = 'oauth_auth_password_flow'
|
12
|
-
|
12
|
+
oauth_auth_client_credentials_flow = 'oauth_auth_client_credentials_flow'
|
13
13
|
oauth_auth_on_behalf_of_flow = 'oauth_auth_on_behalf_of_flow'
|
14
14
|
key_value = 'key_value'
|
15
15
|
kv = 'kv'
|
@@ -32,10 +32,10 @@ class ConnectionPreference(str, Enum):
|
|
32
32
|
return self.value
|
33
33
|
|
34
34
|
class ConnectionAuthType(str, Enum):
|
35
|
-
|
35
|
+
OAUTH2_AUTH_CODE = 'oauth2_auth_code'
|
36
36
|
# OAUTH2_IMPLICIT = 'oauth2_implicit'
|
37
37
|
# OAUTH2_PASSWORD = 'oauth2_password'
|
38
|
-
|
38
|
+
OAUTH2_CLIENT_CREDS = 'oauth2_client_creds'
|
39
39
|
OAUTH_ON_BEHALF_OF_FLOW = 'oauth_on_behalf_of_flow'
|
40
40
|
|
41
41
|
def __str__(self):
|
@@ -63,10 +63,10 @@ class ConnectionType(str, Enum):
|
|
63
63
|
BASIC_AUTH = ConnectionSecurityScheme.BASIC_AUTH.value
|
64
64
|
BEARER_TOKEN = ConnectionSecurityScheme.BEARER_TOKEN.value
|
65
65
|
API_KEY_AUTH = ConnectionSecurityScheme.API_KEY_AUTH.value
|
66
|
-
|
66
|
+
OAUTH2_AUTH_CODE = ConnectionAuthType.OAUTH2_AUTH_CODE.value
|
67
67
|
# OAUTH2_IMPLICIT = ConnectionAuthType.OAUTH2_IMPLICIT.value
|
68
68
|
# OAUTH2_PASSWORD = ConnectionAuthType.OAUTH2_PASSWORD.value
|
69
|
-
|
69
|
+
OAUTH2_CLIENT_CREDS = ConnectionAuthType.OAUTH2_CLIENT_CREDS.value
|
70
70
|
OAUTH_ON_BEHALF_OF_FLOW = ConnectionAuthType.OAUTH_ON_BEHALF_OF_FLOW.value
|
71
71
|
KEY_VALUE = ConnectionSecurityScheme.KEY_VALUE.value
|
72
72
|
|
@@ -77,8 +77,8 @@ class ConnectionType(str, Enum):
|
|
77
77
|
return repr(self.value)
|
78
78
|
|
79
79
|
OAUTH_CONNECTION_TYPES = {
|
80
|
-
|
81
|
-
|
80
|
+
ConnectionType.OAUTH2_AUTH_CODE,
|
81
|
+
ConnectionType.OAUTH2_CLIENT_CREDS,
|
82
82
|
# ConnectionType.OAUTH2_IMPLICIT,
|
83
83
|
# ConnectionType.OAUTH2_PASSWORD,
|
84
84
|
ConnectionType.OAUTH_ON_BEHALF_OF_FLOW,
|
@@ -137,10 +137,15 @@ class ConnectionConfiguration(BaseModel):
|
|
137
137
|
|
138
138
|
@model_validator(mode="after")
|
139
139
|
def validate_config(self):
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
140
|
+
conn_type = None
|
141
|
+
if self.security_scheme == ConnectionSecurityScheme.OAUTH2:
|
142
|
+
conn_type = self.auth_type
|
143
|
+
else:
|
144
|
+
conn_type = self.security_scheme
|
145
|
+
if self.sso and conn_type != ConnectionAuthType.OAUTH_ON_BEHALF_OF_FLOW:
|
146
|
+
raise ValueError(f"SSO not supported for auth scheme '{conn_type}'. SSO can only be used with OAuth auth types")
|
147
|
+
if not self.sso and conn_type == ConnectionAuthType.OAUTH_ON_BEHALF_OF_FLOW:
|
148
|
+
raise ValueError(f"SSO required for '{conn_type}'. Please enable SSO.")
|
144
149
|
if self.sso:
|
145
150
|
if not self.idp_config_data:
|
146
151
|
raise ValueError("For SSO auth 'idp_config_data' is a required field")
|
@@ -167,11 +172,12 @@ class OAuth2TokenCredentials(BaseModel):
|
|
167
172
|
access_token: str
|
168
173
|
url: Optional[str] = None
|
169
174
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
+
class OAuth2AuthCodeCredentials(BaseModel):
|
176
|
+
client_id: str
|
177
|
+
client_secret: str
|
178
|
+
token_url: str
|
179
|
+
authorization_url: str
|
180
|
+
scopes : Optional[List[str]] = None
|
175
181
|
|
176
182
|
# class OAuth2ImplicitCredentials(BaseModel):
|
177
183
|
# client_id: str
|
@@ -183,10 +189,11 @@ class OAuth2TokenCredentials(BaseModel):
|
|
183
189
|
# token_url: str
|
184
190
|
# authorization_url: str
|
185
191
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
192
|
+
class OAuth2ClientCredentials(BaseModel):
|
193
|
+
client_id: str
|
194
|
+
client_secret: str
|
195
|
+
token_url: str
|
196
|
+
scopes : Optional[List[str]] = None
|
190
197
|
|
191
198
|
class OAuthOnBehalfOfCredentials(BaseModel):
|
192
199
|
client_id: str
|
@@ -205,10 +212,10 @@ CREDENTIALS_SET = Union[
|
|
205
212
|
BasicAuthCredentials,
|
206
213
|
BearerTokenAuthCredentials,
|
207
214
|
APIKeyAuthCredentials,
|
208
|
-
|
215
|
+
OAuth2AuthCodeCredentials,
|
209
216
|
# OAuth2ImplicitCredentials,
|
210
217
|
# OAuth2PasswordCredentials,
|
211
|
-
|
218
|
+
OAuth2ClientCredentials,
|
212
219
|
OAuthOnBehalfOfCredentials,
|
213
220
|
KeyValueConnectionCredentials
|
214
221
|
]
|
@@ -219,20 +226,20 @@ CONNECTION_KIND_SCHEME_MAPPING = {
|
|
219
226
|
ConnectionKind.basic: ConnectionSecurityScheme.BASIC_AUTH,
|
220
227
|
ConnectionKind.bearer: ConnectionSecurityScheme.BEARER_TOKEN,
|
221
228
|
ConnectionKind.api_key: ConnectionSecurityScheme.API_KEY_AUTH,
|
222
|
-
|
229
|
+
ConnectionKind.oauth_auth_code_flow: ConnectionSecurityScheme.OAUTH2,
|
223
230
|
# ConnectionKind.oauth_auth_implicit_flow: ConnectionSecurityScheme.OAUTH2,
|
224
231
|
# ConnectionKind.oauth_auth_password_flow: ConnectionSecurityScheme.OAUTH2,
|
225
|
-
|
232
|
+
ConnectionKind.oauth_auth_client_credentials_flow: ConnectionSecurityScheme.OAUTH2,
|
226
233
|
ConnectionKind.oauth_auth_on_behalf_of_flow: ConnectionSecurityScheme.OAUTH2,
|
227
234
|
ConnectionKind.key_value: ConnectionSecurityScheme.KEY_VALUE,
|
228
235
|
ConnectionKind.kv: ConnectionSecurityScheme.KEY_VALUE,
|
229
236
|
}
|
230
237
|
|
231
238
|
CONNECTION_KIND_OAUTH_TYPE_MAPPING = {
|
232
|
-
|
239
|
+
ConnectionKind.oauth_auth_code_flow: ConnectionAuthType.OAUTH2_AUTH_CODE,
|
233
240
|
# ConnectionKind.oauth_auth_implicit_flow: ConnectionAuthType.OAUTH2_IMPLICIT,
|
234
241
|
# ConnectionKind.oauth_auth_password_flow: ConnectionAuthType.OAUTH2_PASSWORD,
|
235
|
-
|
242
|
+
ConnectionKind.oauth_auth_client_credentials_flow: ConnectionAuthType.OAUTH2_CLIENT_CREDS,
|
236
243
|
ConnectionKind.oauth_auth_on_behalf_of_flow: ConnectionAuthType.OAUTH_ON_BEHALF_OF_FLOW,
|
237
244
|
}
|
238
245
|
|
@@ -240,10 +247,10 @@ CONNECTION_TYPE_CREDENTIAL_MAPPING = {
|
|
240
247
|
ConnectionType.BASIC_AUTH: BasicAuthCredentials,
|
241
248
|
ConnectionType.BEARER_TOKEN: BearerTokenAuthCredentials,
|
242
249
|
ConnectionType.API_KEY_AUTH: APIKeyAuthCredentials,
|
243
|
-
|
244
|
-
# ConnectionType.OAUTH2_IMPLICIT:
|
245
|
-
# ConnectionType.OAUTH2_PASSWORD:
|
246
|
-
|
250
|
+
ConnectionType.OAUTH2_AUTH_CODE: OAuth2TokenCredentials,
|
251
|
+
# ConnectionType.OAUTH2_IMPLICIT: OAuth2TokenCredentials,
|
252
|
+
# ConnectionType.OAUTH2_PASSWORD: OAuth2TokenCredentials,
|
253
|
+
ConnectionType.OAUTH2_CLIENT_CREDS: OAuth2TokenCredentials,
|
247
254
|
ConnectionType.OAUTH_ON_BEHALF_OF_FLOW: OAuth2TokenCredentials,
|
248
255
|
ConnectionType.KEY_VALUE: KeyValueConnectionCredentials,
|
249
256
|
}
|
@@ -146,7 +146,6 @@ class ProviderConfig(BaseModel):
|
|
146
146
|
|
147
147
|
# Azure Inference
|
148
148
|
azure_deployment_name: Optional[str] = Field(None, alias="azureDeploymentName")
|
149
|
-
azure_api_version: Optional[str] = Field(None, alias="azureApiVersion")
|
150
149
|
azure_extra_params: Optional[str] = Field(None, alias="azureExtraParams")
|
151
150
|
azure_foundry_url: Optional[str] = Field(None, alias="azureFoundryUrl")
|
152
151
|
|
@@ -0,0 +1,83 @@
|
|
1
|
+
|
2
|
+
import json
|
3
|
+
import logging
|
4
|
+
|
5
|
+
from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
|
6
|
+
from .types import FlowToolBinding, ToolBinding, ToolSpec
|
7
|
+
from .base_tool import BaseTool
|
8
|
+
from .types import ToolPermission
|
9
|
+
|
10
|
+
import json
|
11
|
+
|
12
|
+
logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
class FlowTool(BaseTool):
|
16
|
+
def __init__(self, spec: ToolSpec):
|
17
|
+
BaseTool.__init__(self, spec=spec)
|
18
|
+
|
19
|
+
async def __call__(self, **kwargs):
|
20
|
+
raise RuntimeError('Flow Tools are only available when deployed onto watson orchestrate or the watson '
|
21
|
+
'orchestrate-light runtime')
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def from_spec(file: str) -> 'FlowTool':
|
25
|
+
with open(file, 'r') as f:
|
26
|
+
if file.endswith('.yaml') or file.endswith('.yml'):
|
27
|
+
spec = ToolSpec.model_validate(yaml_safe_load(f))
|
28
|
+
elif file.endswith('.json'):
|
29
|
+
spec = ToolSpec.model_validate(json.load(f))
|
30
|
+
else:
|
31
|
+
raise ValueError('file must end in .json, .yaml, or .yml')
|
32
|
+
|
33
|
+
if spec.binding.openapi is None or spec.binding.openapi is None:
|
34
|
+
raise ValueError('failed to load python tool as the tool had no openapi binding')
|
35
|
+
|
36
|
+
return FlowTool(spec=spec)
|
37
|
+
|
38
|
+
def __repr__(self):
|
39
|
+
return f"FlowTool(model={self.__tool_spec__.binding.flow.model}, name='{self.__tool_spec__.name}', description='{self.__tool_spec__.description}')"
|
40
|
+
|
41
|
+
def __str__(self):
|
42
|
+
return self.__repr__()
|
43
|
+
|
44
|
+
@property
|
45
|
+
def __doc__(self):
|
46
|
+
return self.__tool_spec__.description
|
47
|
+
|
48
|
+
|
49
|
+
def create_flow_json_tool(
|
50
|
+
flow_model: dict,
|
51
|
+
name: str = None,
|
52
|
+
description: str = None,
|
53
|
+
permission: ToolPermission = None,
|
54
|
+
) -> FlowTool:
|
55
|
+
"""
|
56
|
+
Creates a flow tool from a Flow JSON model
|
57
|
+
|
58
|
+
Here we create the basic tool spec. The remaining properties of the tool spec
|
59
|
+
are set by the server when the tool is registered. The server will publish the model to the
|
60
|
+
flow engine and generate the rest of the tool spec based on it's openAPI specification.
|
61
|
+
"""
|
62
|
+
|
63
|
+
spec_name = name
|
64
|
+
spec_permission = permission
|
65
|
+
if spec_name is None:
|
66
|
+
raise ValueError(
|
67
|
+
f"No name provided for tool.")
|
68
|
+
|
69
|
+
spec_description = description
|
70
|
+
if spec_description is None:
|
71
|
+
raise ValueError(
|
72
|
+
f"No description provided for tool.")
|
73
|
+
|
74
|
+
spec = ToolSpec(
|
75
|
+
name=spec_name,
|
76
|
+
description=spec_description,
|
77
|
+
permission=spec_permission
|
78
|
+
)
|
79
|
+
|
80
|
+
spec.binding = ToolBinding(flow=FlowToolBinding(flow_id=name, model=flow_model))
|
81
|
+
|
82
|
+
return FlowTool(spec=spec)
|
83
|
+
|
@@ -14,7 +14,7 @@ from .types import ToolSpec
|
|
14
14
|
from .base_tool import BaseTool
|
15
15
|
from .types import HTTP_METHOD, ToolPermission, ToolRequestBody, ToolResponseBody, \
|
16
16
|
OpenApiToolBinding, \
|
17
|
-
JsonSchemaObject, ToolBinding, OpenApiSecurityScheme
|
17
|
+
JsonSchemaObject, ToolBinding, OpenApiSecurityScheme, CallbackBinding
|
18
18
|
|
19
19
|
import json
|
20
20
|
|
@@ -134,6 +134,7 @@ def create_openapi_json_tool(
|
|
134
134
|
description=spec_description,
|
135
135
|
permission=spec_permission
|
136
136
|
)
|
137
|
+
spec.is_async = 'callbacks' in route_spec
|
137
138
|
|
138
139
|
spec.input_schema = input_schema or ToolRequestBody(
|
139
140
|
type='object',
|
@@ -203,13 +204,50 @@ def create_openapi_json_tool(
|
|
203
204
|
|
204
205
|
security.append(security_schemes_map[name])
|
205
206
|
|
206
|
-
|
207
|
+
# If it's an async tool, add callback binding
|
208
|
+
if spec.is_async:
|
209
|
+
|
210
|
+
|
211
|
+
callbacks = route_spec.get('callbacks', {})
|
212
|
+
callback_name = next(iter(callbacks.keys()))
|
213
|
+
callback_spec = callbacks[callback_name]
|
214
|
+
callback_path = next(iter(callback_spec.keys()))
|
215
|
+
callback_method = next(iter(callback_spec[callback_path].keys()))
|
216
|
+
|
217
|
+
# Phase 1: Create a separate input schema for callback that excludes callbackUrl
|
218
|
+
# Note: Currently assuming the callback URL parameter will be named 'callbackUrl' in the OpenAPI spec
|
219
|
+
# Future phases will handle other naming conventions
|
220
|
+
callback_input_schema = ToolRequestBody(
|
221
|
+
type='object',
|
222
|
+
properties={k: v for k, v in spec.input_schema.properties.items() if not k.endswith('_callbackUrl')},
|
223
|
+
required=[r for r in spec.input_schema.required if not r.endswith('_callbackUrl')]
|
224
|
+
)
|
225
|
+
|
226
|
+
if callback_input_schema:
|
227
|
+
spec.input_schema = callback_input_schema
|
228
|
+
|
229
|
+
callback_binding = CallbackBinding(
|
230
|
+
callback_url=callback_path,
|
231
|
+
method=callback_method.upper(),
|
232
|
+
input_schema=callback_input_schema,
|
233
|
+
output_schema=spec.output_schema
|
234
|
+
)
|
235
|
+
|
236
|
+
else:
|
237
|
+
callback_binding = None
|
238
|
+
|
239
|
+
openapi_binding = OpenApiToolBinding(
|
207
240
|
http_path=http_path,
|
208
241
|
http_method=http_method,
|
209
242
|
security=security,
|
210
243
|
servers=servers,
|
211
244
|
connection_id=connection_id
|
212
|
-
)
|
245
|
+
)
|
246
|
+
|
247
|
+
if callback_binding is not None:
|
248
|
+
openapi_binding.callback = callback_binding
|
249
|
+
|
250
|
+
spec.binding = ToolBinding(openapi=openapi_binding)
|
213
251
|
|
214
252
|
return OpenAPITool(spec=spec)
|
215
253
|
|
@@ -95,7 +95,8 @@ def _fix_optional(schema):
|
|
95
95
|
def _validate_input_schema(input_schema: ToolRequestBody) -> None:
|
96
96
|
props = input_schema.properties
|
97
97
|
for prop in props:
|
98
|
-
|
98
|
+
property_schema = props.get(prop)
|
99
|
+
if not (property_schema.type or property_schema.anyOf):
|
99
100
|
logger.warning(f"Missing type hint for tool property '{prop}' defaulting to 'str'. To remove this warning add a type hint to the property in the tools signature. See Python docs for guidance: https://docs.python.org/3/library/typing.html")
|
100
101
|
|
101
102
|
def _validate_join_tool_func(fn: Callable, sig: inspect.Signature | None = None, name: str | None = None) -> None:
|
@@ -93,6 +93,11 @@ class OpenApiSecurityScheme(BaseModel):
|
|
93
93
|
|
94
94
|
HTTP_METHOD = Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
95
95
|
|
96
|
+
class CallbackBinding(BaseModel):
|
97
|
+
callback_url: str
|
98
|
+
method: HTTP_METHOD
|
99
|
+
input_schema: ToolRequestBody
|
100
|
+
output_schema: ToolResponseBody
|
96
101
|
|
97
102
|
class OpenApiToolBinding(BaseModel):
|
98
103
|
http_method: HTTP_METHOD
|
@@ -101,6 +106,7 @@ class OpenApiToolBinding(BaseModel):
|
|
101
106
|
security: Optional[List[OpenApiSecurityScheme]] = None
|
102
107
|
servers: Optional[List[str]] = None
|
103
108
|
connection_id: str | None = None
|
109
|
+
callback: CallbackBinding = None
|
104
110
|
|
105
111
|
@model_validator(mode='after')
|
106
112
|
def validate_openapi_tool_binding(self):
|
@@ -142,6 +148,10 @@ class McpToolBinding(BaseModel):
|
|
142
148
|
source: str
|
143
149
|
connections: Dict[str, str] | None
|
144
150
|
|
151
|
+
class FlowToolBinding(BaseModel):
|
152
|
+
flow_id: str
|
153
|
+
model: Optional[dict] = None
|
154
|
+
|
145
155
|
class ToolBinding(BaseModel):
|
146
156
|
openapi: OpenApiToolBinding = None
|
147
157
|
python: PythonToolBinding = None
|
@@ -149,6 +159,7 @@ class ToolBinding(BaseModel):
|
|
149
159
|
skill: SkillToolBinding = None
|
150
160
|
client_side: ClientSideToolBinding = None
|
151
161
|
mcp: McpToolBinding = None
|
162
|
+
flow: FlowToolBinding = None
|
152
163
|
|
153
164
|
@model_validator(mode='after')
|
154
165
|
def validate_binding_type(self) -> 'ToolBinding':
|
@@ -158,7 +169,8 @@ class ToolBinding(BaseModel):
|
|
158
169
|
self.wxflows is not None,
|
159
170
|
self.skill is not None,
|
160
171
|
self.client_side is not None,
|
161
|
-
self.mcp is not None
|
172
|
+
self.mcp is not None,
|
173
|
+
self.flow is not None
|
162
174
|
]
|
163
175
|
if sum(bindings) == 0:
|
164
176
|
raise ValueError("One binding must be set")
|
@@ -175,6 +187,7 @@ class ToolSpec(BaseModel):
|
|
175
187
|
output_schema: ToolResponseBody = None
|
176
188
|
binding: ToolBinding = None
|
177
189
|
toolkit_id: str | None = None
|
190
|
+
is_async: bool = False
|
178
191
|
|
179
192
|
def is_custom_join_tool(self) -> bool:
|
180
193
|
if self.binding.python is None:
|
@@ -137,7 +137,7 @@ def agent_create(
|
|
137
137
|
List[str],
|
138
138
|
typer.Option(
|
139
139
|
"--knowledge-bases",
|
140
|
-
help="A list of
|
140
|
+
help="A list of knowledge bases names you wish for the agent to be able to utilise. Format --knowledge-bases base1 --knowledge-bases base2 ...",
|
141
141
|
),
|
142
142
|
] = None,
|
143
143
|
output_file: Annotated[
|
@@ -148,6 +148,21 @@ def agent_create(
|
|
148
148
|
help="Write the agent definition out to a YAML (.yaml/.yml) file or a JSON (.json) file.",
|
149
149
|
),
|
150
150
|
] = None,
|
151
|
+
context_access_enabled: Annotated[
|
152
|
+
bool,
|
153
|
+
typer.Option(
|
154
|
+
"--context-access-enabled",
|
155
|
+
help="Whether the agent has access to context variables (default: True)",
|
156
|
+
),
|
157
|
+
] = True,
|
158
|
+
context_variables: Annotated[
|
159
|
+
List[str],
|
160
|
+
typer.Option(
|
161
|
+
"--context-variable",
|
162
|
+
"-v",
|
163
|
+
help="A list of context variable names the agent can access. Format: --context-variable var1 --context-variable var2 ... or -v var1 -v var2 ...",
|
164
|
+
),
|
165
|
+
] = None,
|
151
166
|
):
|
152
167
|
chat_params_dict = json.loads(chat_params) if chat_params else {}
|
153
168
|
config_dict = json.loads(config) if config else {}
|
@@ -177,6 +192,8 @@ def agent_create(
|
|
177
192
|
nickname=nickname,
|
178
193
|
app_id=app_id,
|
179
194
|
output_file=output_file,
|
195
|
+
context_access_enabled=context_access_enabled,
|
196
|
+
context_variables=context_variables,
|
180
197
|
)
|
181
198
|
agents_controller.publish_or_update_agents([agent])
|
182
199
|
|