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.
Files changed (71) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
  3. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
  4. ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
  5. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
  7. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
  8. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
  9. ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
  10. ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
  11. ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
  13. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
  14. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
  15. ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
  17. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
  18. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
  19. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  20. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
  21. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
  23. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
  24. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
  26. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
  29. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
  30. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
  31. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
  32. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
  33. ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
  36. ibm_watsonx_orchestrate/cli/config.py +2 -0
  37. ibm_watsonx_orchestrate/cli/main.py +6 -0
  38. ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
  39. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
  40. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
  41. ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
  42. ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
  43. ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
  44. ibm_watsonx_orchestrate/client/credentials.py +4 -0
  45. ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
  46. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
  47. ibm_watsonx_orchestrate/client/service_instance.py +42 -1
  48. ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
  49. ibm_watsonx_orchestrate/client/utils.py +37 -2
  50. ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
  51. ibm_watsonx_orchestrate/docker/default.env +40 -15
  52. ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
  53. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
  54. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
  55. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
  56. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
  57. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
  58. ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
  59. ibm_watsonx_orchestrate/run/connections.py +4 -4
  60. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
  61. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
  62. ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
  63. ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
  64. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
  65. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
  66. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
  67. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
  68. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
  69. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
  70. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
  71. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@
5
5
 
6
6
  pkg_name = "ibm-watsonx-orchestrate"
7
7
 
8
- __version__ = "1.5.1"
8
+ __version__ = "1.6.0"
9
9
 
10
10
 
11
11
 
@@ -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" #provider type returned from an assistant agent
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,2 @@
1
+ from .prompts import AgentPrompt, StarterPrompts
2
+ from .welcome_content import WelcomeContent
@@ -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
- # OAuth2AuthCodeCredentials,
19
- # OAuth2ClientCredentials,
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
- # oauth_auth_code_flow = 'oauth_auth_code_flow'
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
- # oauth_auth_client_credentials_flow = 'oauth_auth_client_credentials_flow'
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
- # OAUTH2_AUTH_CODE = 'oauth2_auth_code'
35
+ OAUTH2_AUTH_CODE = 'oauth2_auth_code'
36
36
  # OAUTH2_IMPLICIT = 'oauth2_implicit'
37
37
  # OAUTH2_PASSWORD = 'oauth2_password'
38
- # OAUTH2_CLIENT_CREDS = 'oauth2_client_creds'
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
- # OAUTH2_AUTH_CODE = ConnectionAuthType.OAUTH2_AUTH_CODE.value
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
- # OAUTH2_CLIENT_CREDS = ConnectionAuthType.OAUTH2_CLIENT_CREDS.value
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
- # ConnectionType.OAUTH2_AUTH_CODE,
81
- # ConnectionType.OAUTH2_CLIENT_CREDS,
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
- if self.sso and self.security_scheme != ConnectionSecurityScheme.OAUTH2:
141
- raise ValueError(f"SSO not supported for auth scheme '{self.security_scheme}'. SSO can only be used with OAuth auth types")
142
- if not self.sso and self.security_scheme == ConnectionSecurityScheme.OAUTH2:
143
- raise ValueError(f"SSO required for OAuth auth schemes. Please enable SSO.")
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
- # class OAuth2AuthCodeCredentials(BaseModel):
171
- # client_id: str
172
- # client_secret: str
173
- # token_url: str
174
- # authorization_url: str
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
- # class OAuth2ClientCredentials(BaseModel):
187
- # client_id: str
188
- # client_secret: str
189
- # token_url: str
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
- # OAuth2AuthCodeCredentials,
215
+ OAuth2AuthCodeCredentials,
209
216
  # OAuth2ImplicitCredentials,
210
217
  # OAuth2PasswordCredentials,
211
- # OAuth2ClientCredentials,
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
- # ConnectionKind.oauth_auth_code_flow: ConnectionSecurityScheme.OAUTH2,
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
- # ConnectionKind.oauth_auth_client_credentials_flow: ConnectionSecurityScheme.OAUTH2,
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
- # ConnectionKind.oauth_auth_code_flow: ConnectionAuthType.OAUTH2_AUTH_CODE,
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
- # ConnectionKind.oauth_auth_client_credentials_flow: ConnectionAuthType.OAUTH2_CLIENT_CREDS,
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
- # ConnectionType.OAUTH2_AUTH_CODE: BearerTokenAuthCredentials,
244
- # ConnectionType.OAUTH2_IMPLICIT: BearerTokenAuthCredentials,
245
- # ConnectionType.OAUTH2_PASSWORD: BearerTokenAuthCredentials,
246
- # ConnectionType.OAUTH2_CLIENT_CREDS: BearerTokenAuthCredentials,
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
  }
@@ -17,7 +17,7 @@ class ModelPolicyRetry(BaseModel):
17
17
  on_status_codes: List[int] = None
18
18
 
19
19
  class ModelPolicyTarget(BaseModel):
20
- weight: float = None
20
+ weight: float | None = None
21
21
  model_name: str = None
22
22
 
23
23
  class ModelPolicyInner(BaseModel):
@@ -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
- spec.binding = ToolBinding(openapi=OpenApiToolBinding(
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
- if not props.get(prop).type:
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 knowlege bases names you wish for the agent to be able to utilise. Format --knowledge-bases base1 --knowledge-bases base2 ...",
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