ibm-watsonx-orchestrate 1.0.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 (89) hide show
  1. ibm_watsonx_orchestrate/__init__.py +28 -0
  2. ibm_watsonx_orchestrate/agent_builder/__init__.py +0 -0
  3. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +5 -0
  4. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +27 -0
  5. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +28 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +28 -0
  7. ibm_watsonx_orchestrate/agent_builder/agents/types.py +204 -0
  8. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +27 -0
  9. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +123 -0
  10. ibm_watsonx_orchestrate/agent_builder/connections/types.py +260 -0
  11. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +27 -0
  12. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +59 -0
  13. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +243 -0
  14. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +4 -0
  15. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +36 -0
  16. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +332 -0
  17. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +195 -0
  18. ibm_watsonx_orchestrate/agent_builder/tools/types.py +162 -0
  19. ibm_watsonx_orchestrate/agent_builder/utils/__init__.py +0 -0
  20. ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +149 -0
  21. ibm_watsonx_orchestrate/cli/__init__.py +0 -0
  22. ibm_watsonx_orchestrate/cli/commands/__init__.py +0 -0
  23. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +192 -0
  24. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +660 -0
  25. ibm_watsonx_orchestrate/cli/commands/channels/channels_command.py +15 -0
  26. ibm_watsonx_orchestrate/cli/commands/channels/channels_controller.py +16 -0
  27. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -0
  28. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_command.py +32 -0
  29. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +141 -0
  30. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +43 -0
  31. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +307 -0
  32. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +517 -0
  33. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +78 -0
  34. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +189 -0
  35. ibm_watsonx_orchestrate/cli/commands/environment/types.py +9 -0
  36. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +79 -0
  37. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +201 -0
  38. ibm_watsonx_orchestrate/cli/commands/login/login_command.py +17 -0
  39. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +128 -0
  40. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +623 -0
  41. ibm_watsonx_orchestrate/cli/commands/settings/__init__.py +0 -0
  42. ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py +0 -0
  43. ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/__init__.py +0 -0
  44. ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/langfuse_command.py +175 -0
  45. ibm_watsonx_orchestrate/cli/commands/settings/observability/observability_command.py +11 -0
  46. ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py +10 -0
  47. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +85 -0
  48. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +564 -0
  49. ibm_watsonx_orchestrate/cli/commands/tools/types.py +10 -0
  50. ibm_watsonx_orchestrate/cli/config.py +226 -0
  51. ibm_watsonx_orchestrate/cli/main.py +32 -0
  52. ibm_watsonx_orchestrate/client/__init__.py +0 -0
  53. ibm_watsonx_orchestrate/client/agents/agent_client.py +46 -0
  54. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +38 -0
  55. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +38 -0
  56. ibm_watsonx_orchestrate/client/analytics/__init__.py +0 -0
  57. ibm_watsonx_orchestrate/client/analytics/llm/__init__.py +0 -0
  58. ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +50 -0
  59. ibm_watsonx_orchestrate/client/base_api_client.py +113 -0
  60. ibm_watsonx_orchestrate/client/base_service_instance.py +10 -0
  61. ibm_watsonx_orchestrate/client/client.py +71 -0
  62. ibm_watsonx_orchestrate/client/client_errors.py +359 -0
  63. ibm_watsonx_orchestrate/client/connections/__init__.py +10 -0
  64. ibm_watsonx_orchestrate/client/connections/connections_client.py +162 -0
  65. ibm_watsonx_orchestrate/client/connections/utils.py +27 -0
  66. ibm_watsonx_orchestrate/client/credentials.py +123 -0
  67. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +46 -0
  68. ibm_watsonx_orchestrate/client/local_service_instance.py +91 -0
  69. ibm_watsonx_orchestrate/client/service_instance.py +73 -0
  70. ibm_watsonx_orchestrate/client/tools/tool_client.py +41 -0
  71. ibm_watsonx_orchestrate/client/utils.py +95 -0
  72. ibm_watsonx_orchestrate/docker/compose-lite.yml +595 -0
  73. ibm_watsonx_orchestrate/docker/default.env +125 -0
  74. ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl +0 -0
  75. ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz +0 -0
  76. ibm_watsonx_orchestrate/docker/start-up.sh +61 -0
  77. ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -0
  78. ibm_watsonx_orchestrate/run/__init__.py +0 -0
  79. ibm_watsonx_orchestrate/run/connections.py +40 -0
  80. ibm_watsonx_orchestrate/utils/__init__.py +0 -0
  81. ibm_watsonx_orchestrate/utils/logging/__init__.py +0 -0
  82. ibm_watsonx_orchestrate/utils/logging/logger.py +26 -0
  83. ibm_watsonx_orchestrate/utils/logging/logging.yaml +18 -0
  84. ibm_watsonx_orchestrate/utils/utils.py +15 -0
  85. ibm_watsonx_orchestrate-1.0.0.dist-info/METADATA +34 -0
  86. ibm_watsonx_orchestrate-1.0.0.dist-info/RECORD +89 -0
  87. ibm_watsonx_orchestrate-1.0.0.dist-info/WHEEL +4 -0
  88. ibm_watsonx_orchestrate-1.0.0.dist-info/entry_points.txt +2 -0
  89. ibm_watsonx_orchestrate-1.0.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,28 @@
1
+ # -----------------------------------------------------------------------------------------
2
+ # (C) Copyright IBM Corp. 2023-2024.
3
+ # https://opensource.org/licenses/BSD-3-Clause
4
+ # -----------------------------------------------------------------------------------------
5
+
6
+ pkg_name = "ibm-watsonx-orchestrate"
7
+
8
+ __version__ = "1.0.0"
9
+
10
+
11
+
12
+ try:
13
+ from importlib.metadata import version
14
+
15
+ ver = version(pkg_name)
16
+
17
+ except (ModuleNotFoundError, AttributeError):
18
+ from importlib_metadata import version as imp_lib_ver
19
+
20
+ ver = imp_lib_ver(pkg_name)
21
+
22
+ from ibm_watsonx_orchestrate.client.client import Client
23
+ from ibm_watsonx_orchestrate.utils.logging.logger import setup_logging
24
+
25
+ Client.version = ver
26
+ __version__ = ver
27
+ setup_logging()
28
+
File without changes
@@ -0,0 +1,5 @@
1
+ from .agent import Agent, AgentSpec
2
+ from .external_agent import ExternalAgent, ExternalAgentSpec
3
+ from .assistant_agent import AssistantAgent, AssistantAgentSpec
4
+ # from .types import AgentKind, AgentStyle, ExternalAgentConfig, AssistantAgentConfig, SpecVersion
5
+ from .types import AgentKind, AgentStyle, SpecVersion, ExternalAgentAuthScheme, AgentProvider, AssistantAgentConfig
@@ -0,0 +1,27 @@
1
+ import json
2
+ from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
+ from .types import AgentSpec
4
+
5
+
6
+ class Agent(AgentSpec):
7
+
8
+ @staticmethod
9
+ def from_spec(file: str) -> 'Agent':
10
+ with open(file, 'r') as f:
11
+ if file.endswith('.yaml') or file.endswith('.yml'):
12
+ content = yaml_safe_load(f)
13
+ elif file.endswith('.json'):
14
+ content = json.load(f)
15
+ else:
16
+ raise ValueError('file must end in .json, .yaml, or .yml')
17
+ if not content.get("spec_version"):
18
+ raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
19
+ agent = Agent.model_validate(content)
20
+
21
+ return agent
22
+
23
+ def __repr__(self):
24
+ return f"Agent(name='{self.name}', description='{self.description}')"
25
+
26
+ def __str__(self):
27
+ return self.__repr__()
@@ -0,0 +1,28 @@
1
+ import json
2
+ from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
+ from .types import AssistantAgentSpec
4
+
5
+
6
+ class AssistantAgent(AssistantAgentSpec):
7
+
8
+ @staticmethod
9
+ def from_spec(file: str) -> 'AssistantAgent':
10
+ with open(file, 'r') as f:
11
+ if file.endswith('.yaml') or file.endswith('.yml'):
12
+ content = yaml_safe_load(f)
13
+ elif file.endswith('.json'):
14
+ content = json.load(f)
15
+ else:
16
+ raise ValueError('file must end in .json, .yaml, or .yml')
17
+
18
+ if not content.get("spec_version"):
19
+ raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
+ agent = AssistantAgent.model_validate(content)
21
+
22
+ return agent
23
+
24
+ def __repr__(self):
25
+ return f"AssistantAgent(name='{self.name}', description='{self.description}')"
26
+
27
+ def __str__(self):
28
+ return self.__repr__()
@@ -0,0 +1,28 @@
1
+ import json
2
+ from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
3
+ from .types import ExternalAgentSpec
4
+
5
+
6
+ class ExternalAgent(ExternalAgentSpec):
7
+
8
+ @staticmethod
9
+ def from_spec(file: str) -> 'ExternalAgent':
10
+ with open(file, 'r') as f:
11
+ if file.endswith('.yaml') or file.endswith('.yml'):
12
+ content = yaml_safe_load(f)
13
+ elif file.endswith('.json'):
14
+ content = json.load(f)
15
+ else:
16
+ raise ValueError('file must end in .json, .yaml, or .yml')
17
+
18
+ if not content.get("spec_version"):
19
+ raise ValueError(f"Field 'spec_version' not provided. Please ensure provided spec conforms to a valid spec format")
20
+ agent = ExternalAgent.model_validate(content)
21
+
22
+ return agent
23
+
24
+ def __repr__(self):
25
+ return f"ExternalAgent(name='{self.name}', description='{self.description}')"
26
+
27
+ def __str__(self):
28
+ return self.__repr__()
@@ -0,0 +1,204 @@
1
+ import json
2
+ import yaml
3
+ from enum import Enum
4
+ from typing import List, Optional, Dict
5
+ from pydantic import BaseModel, model_validator, ConfigDict
6
+ from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool
7
+ from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import KnowledgeBaseSpec
8
+ from pydantic import Field, AliasChoices
9
+ from typing import Annotated
10
+
11
+ # TO-DO: this is just a placeholder. Will update this later to align with backend
12
+ DEFAULT_LLM = "watsonx/meta-llama/llama-3-1-70b-instruct"
13
+
14
+ class SpecVersion(str, Enum):
15
+ V1 = "v1"
16
+
17
+
18
+ class AgentKind(str, Enum):
19
+ NATIVE = "native"
20
+ EXTERNAL = "external"
21
+ ASSISTANT = "assistant"
22
+
23
+ class ExternalAgentAuthScheme(str, Enum):
24
+ BEARER_TOKEN = 'BEARER_TOKEN'
25
+ API_KEY = "API_KEY"
26
+ NONE = 'NONE'
27
+
28
+ class AgentProvider(str, Enum):
29
+ WXAI = "wx.ai"
30
+ EXT_CHAT = "external_chat"
31
+ SALESFORCE = "salesforce"
32
+ WATSONX = "watsonx" #provider type returned from an assistant agent
33
+
34
+
35
+ class AssistantAgentAuthType(str, Enum):
36
+ ICP_IAM = "ICP_IAM"
37
+ IBM_CLOUD_IAM = "IBM_CLOUD_IAM"
38
+ MCSP = "MCSP"
39
+ BEARER_TOKEN = "BEARER_TOKEN"
40
+ HIDDEN = "<hidden>"
41
+
42
+
43
+ class BaseAgentSpec(BaseModel):
44
+ spec_version: SpecVersion = None
45
+ kind: AgentKind
46
+ id: Optional[Annotated[str, Field(json_schema_extra={"min_length_str": 1})]] = None
47
+ name: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
48
+ description: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
49
+
50
+ def dump_spec(self, file: str) -> None:
51
+ dumped = self.model_dump(mode='json', exclude_unset=True, exclude_none=True)
52
+ with open(file, 'w') as f:
53
+ if file.endswith('.yaml') or file.endswith('.yml'):
54
+ yaml.dump(dumped, f, sort_keys=False)
55
+ elif file.endswith('.json'):
56
+ json.dump(dumped, f, indent=2)
57
+ else:
58
+ raise ValueError('file must end in .json, .yaml, or .yml')
59
+
60
+ def dumps_spec(self) -> str:
61
+ dumped = self.model_dump(mode='json', exclude_none=True)
62
+ return json.dumps(dumped, indent=2)
63
+
64
+ # ===============================
65
+ # NATIVE AGENT TYPES
66
+ # ===============================
67
+
68
+ class AgentStyle(str, Enum):
69
+ DEFAULT = "default"
70
+ REACT = "react"
71
+
72
+ class AgentSpec(BaseAgentSpec):
73
+ model_config = ConfigDict(arbitrary_types_allowed=True)
74
+
75
+ kind: AgentKind = AgentKind.NATIVE
76
+ llm: str = DEFAULT_LLM
77
+ style: AgentStyle = AgentStyle.DEFAULT
78
+ instructions: Annotated[Optional[str], Field(json_schema_extra={"min_length_str":1})] = None
79
+ collaborators: Optional[List[str]] | Optional[List['BaseAgentSpec']] = []
80
+ tools: Optional[List[str]] | Optional[List['BaseTool']] = []
81
+ hidden: bool = False
82
+ knowledge_base: Optional[List[str]] | Optional[List['KnowledgeBaseSpec']] = []
83
+
84
+
85
+ def __init__(self, *args, **kwargs):
86
+ if "tools" in kwargs and kwargs["tools"]:
87
+ kwargs["tools"] = [x.__tool_spec__.name if isinstance(x, BaseTool) else x for x in kwargs["tools"]]
88
+ if "collaborators" in kwargs and kwargs["collaborators"]:
89
+ kwargs["collaborators"] = [x.name if isinstance(x, BaseAgentSpec) else x for x in kwargs["collaborators"]]
90
+ super().__init__(*args, **kwargs)
91
+
92
+ @model_validator(mode="before")
93
+ def validate_fields(cls, values):
94
+ return validate_agent_fields(values)
95
+
96
+ @model_validator(mode="after")
97
+ def validate_kind(self):
98
+ if self.kind != AgentKind.NATIVE:
99
+ raise ValueError(f"The specified kind '{self.kind}' cannot be used to create a native agent.")
100
+ return self
101
+
102
+ def validate_agent_fields(values: dict) -> dict:
103
+ # Check for empty strings or whitespace
104
+ for field in ["id", "name", "kind", "description", "collaborators", "tools"]:
105
+ value = values.get(field)
106
+ if value and not str(value).strip():
107
+ raise ValueError(f"{field} cannot be empty or just whitespace")
108
+
109
+ name = values.get("name")
110
+ collaborators = values.get("collaborators", []) if values.get("collaborators", []) else []
111
+ for collaborator in collaborators:
112
+ if collaborator == name:
113
+ raise ValueError(f"Circular reference detected. The agent '{name}' cannot contain itself as a collaborator")
114
+
115
+
116
+ return values
117
+
118
+ # ===============================
119
+ # EXTERNAL AGENT TYPES
120
+ # ===============================
121
+
122
+ class ExternalAgentConfig(BaseModel):
123
+ hidden: bool = False
124
+ enable_cot: bool = False
125
+
126
+ class ExternalAgentSpec(BaseAgentSpec):
127
+ model_config = ConfigDict(arbitrary_types_allowed=True)
128
+
129
+ kind: AgentKind = AgentKind.EXTERNAL
130
+ title: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
131
+ tags: Optional[List[str]] = None
132
+ api_url: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
133
+ auth_scheme: ExternalAgentAuthScheme = ExternalAgentAuthScheme.NONE
134
+ auth_config: dict = {}
135
+ provider: AgentProvider = AgentProvider.EXT_CHAT
136
+ chat_params: dict = None
137
+ config: ExternalAgentConfig = ExternalAgentConfig()
138
+ nickname: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
139
+ app_id: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
140
+ connection_id: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
141
+
142
+ @model_validator(mode="before")
143
+ def validate_fields_for_external(cls, values):
144
+ return validate_external_agent_fields(values)
145
+
146
+ @model_validator(mode="after")
147
+ def validate_kind_for_external(self):
148
+ if self.kind != AgentKind.EXTERNAL:
149
+ raise ValueError(f"The specified kind '{self.kind}' cannot be used to create an external agent.")
150
+ return self
151
+
152
+ def validate_external_agent_fields(values: dict) -> dict:
153
+ # Check for empty strings or whitespace
154
+ for field in ["name", "kind", "description", "title", "tags", "api_url", "chat_params", "nickname", "app_id"]:
155
+ value = values.get(field)
156
+ if value and not str(value).strip():
157
+ raise ValueError(f"{field} cannot be empty or just whitespace")
158
+
159
+ return values
160
+
161
+ # # ===============================
162
+ # # ASSISTANT AGENT TYPES
163
+ # # ===============================
164
+
165
+ class AssistantAgentConfig(BaseModel):
166
+ api_version: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
167
+ assistant_id: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
168
+ crn: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
169
+ service_instance_url: Annotated[str | None, Field(validation_alias=AliasChoices('instance_url', 'service_instance_url'), serialization_alias='service_instance_url')] = None
170
+ environment_id: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
171
+ auth_type: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
172
+ connection_id: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
173
+ api_key: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
174
+ authorization_url: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
175
+ auth_type: AssistantAgentAuthType = AssistantAgentAuthType.MCSP
176
+
177
+ class AssistantAgentSpec(BaseAgentSpec):
178
+ model_config = ConfigDict(arbitrary_types_allowed=True)
179
+
180
+ kind: AgentKind = AgentKind.ASSISTANT
181
+ title: Annotated[str, Field(json_schema_extra={"min_length_str":1})]
182
+ tags: Optional[List[str]] = None
183
+ config: AssistantAgentConfig = AssistantAgentConfig()
184
+ nickname: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
185
+ connection_id: Annotated[str | None, Field(json_schema_extra={"min_length_str":1})] = None
186
+
187
+ @model_validator(mode="before")
188
+ def validate_fields_for_external(cls, values):
189
+ return validate_assistant_agent_fields(values)
190
+
191
+ @model_validator(mode="after")
192
+ def validate_kind_for_external(self):
193
+ if self.kind != AgentKind.ASSISTANT:
194
+ raise ValueError(f"The specified kind '{self.kind}' cannot be used to create an assistant agent.")
195
+ return self
196
+
197
+ def validate_assistant_agent_fields(values: dict) -> dict:
198
+ # Check for empty strings or whitespace
199
+ for field in ["name", "kind", "description", "title", "tags", "nickname", "app_id"]:
200
+ value = values.get(field)
201
+ if value and not str(value).strip():
202
+ raise ValueError(f"{field} cannot be empty or just whitespace")
203
+
204
+ return values
@@ -0,0 +1,27 @@
1
+ from .connections import (
2
+ get_application_connection_credentials,
3
+ get_connection_type
4
+ )
5
+ from .types import (
6
+ ConnectionType,
7
+ ConnectionKind,
8
+ ConnectionAuthType,
9
+ ConnectionConfiguration,
10
+ ConnectionEnvironment,
11
+ ConnectionPreference,
12
+ ConnectionSecurityScheme,
13
+ CREDENTIALS,
14
+ BasicAuthCredentials,
15
+ BearerTokenAuthCredentials,
16
+ APIKeyAuthCredentials,
17
+ OAuth2TokenCredentials,
18
+ # OAuth2AuthCodeCredentials,
19
+ # OAuth2ClientCredentials,
20
+ # OAuth2ImplicitCredentials,
21
+ # OAuth2PasswordCredentials,
22
+ OAuthOnBehalfOfCredentials,
23
+ KeyValueConnectionCredentials,
24
+ CONNECTION_KIND_SCHEME_MAPPING,
25
+ CONNECTION_TYPE_CREDENTIAL_MAPPING,
26
+ ExpectedCredentials
27
+ )
@@ -0,0 +1,123 @@
1
+ import os
2
+ import logging
3
+ from typing import List
4
+ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
5
+ BasicAuthCredentials,
6
+ BearerTokenAuthCredentials,
7
+ APIKeyAuthCredentials,
8
+ OAuth2TokenCredentials,
9
+ KeyValueConnectionCredentials,
10
+ ConnectionType,
11
+ CREDENTIALS,
12
+ CONNECTION_TYPE_CREDENTIAL_MAPPING
13
+ )
14
+
15
+ from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _PREFIX_TEMPLATE = "WXO_CONNECTION_{app_id}_"
20
+
21
+ connection_type_requirements_mapping = {
22
+ BasicAuthCredentials: ["username", "password"],
23
+ BearerTokenAuthCredentials: ["token"],
24
+ APIKeyAuthCredentials: ["api_key"],
25
+ OAuth2TokenCredentials: ["access_token"],
26
+ KeyValueConnectionCredentials: None
27
+ }
28
+
29
+ def _clean_env_vars(vars: dict[str:str], requirements: List[str], app_id: str) -> dict[str,str]:
30
+ base_prefix = _PREFIX_TEMPLATE.format(app_id=app_id)
31
+
32
+ required_env_vars = {}
33
+ missing_requirements = []
34
+ for requirement in requirements:
35
+ key = base_prefix + requirement
36
+ value = vars.get(key)
37
+ if value:
38
+ required_env_vars[key] = value
39
+ else:
40
+ missing_requirements.append(key)
41
+
42
+ # Get value from optional url env var
43
+ key = base_prefix + "url"
44
+ value = vars.get(key)
45
+ required_env_vars[key] = value
46
+
47
+ if len(missing_requirements) > 0:
48
+ missing_requirements_str = ", ".join(missing_requirements)
49
+ message = f"Missing requirement environment variables '{missing_requirements_str}' for connection '{app_id}'"
50
+ logger.error(message)
51
+ raise ValueError(message)
52
+
53
+ return required_env_vars
54
+
55
+ def _build_credentials_model(credentials_type: type[CREDENTIALS], vars: dict[str,str], base_prefix: str) -> type[CREDENTIALS]:
56
+ requirements = connection_type_requirements_mapping[credentials_type]
57
+
58
+ if requirements:
59
+ requirements.append("url")
60
+ model_dict={}
61
+
62
+ for requirement in requirements:
63
+ model_dict[requirement] = vars[base_prefix+requirement]
64
+ return credentials_type(
65
+ **model_dict
66
+ )
67
+ else:
68
+ # Strip the default prefix
69
+ model_dict = {}
70
+ for key in vars:
71
+ new_key = key.removeprefix(base_prefix)
72
+ model_dict[new_key] = vars[key]
73
+ return credentials_type(
74
+ model_dict
75
+ )
76
+
77
+
78
+ def _validate_schema_type(requested_type: ConnectionType, expected_type: ConnectionType) -> bool:
79
+ return expected_type == requested_type
80
+
81
+ def _get_credentials_model(connection_type: ConnectionType, app_id: str) -> type[CREDENTIALS]:
82
+ base_prefix = _PREFIX_TEMPLATE.format(app_id=app_id)
83
+ variables = {}
84
+ for key, value in os.environ.items():
85
+ if key.startswith(base_prefix):
86
+ variables[key] = value
87
+
88
+ credentials_type = CONNECTION_TYPE_CREDENTIAL_MAPPING[connection_type]
89
+
90
+ requirements = connection_type_requirements_mapping.get(credentials_type)
91
+ if requirements:
92
+ variables = _clean_env_vars(vars=variables, requirements=requirements, app_id=app_id)
93
+
94
+ return _build_credentials_model(credentials_type=credentials_type, vars=variables, base_prefix=base_prefix)
95
+
96
+ def get_connection_type(app_id: str) -> ConnectionType:
97
+ sanitized_app_id = sanatize_app_id(app_id=app_id)
98
+ expected_schema_key = f"WXO_SECURITY_SCHEMA_{sanitized_app_id}"
99
+ expected_schema = os.environ.get(expected_schema_key)
100
+
101
+ if not expected_schema:
102
+ message = f"No credentials found for connections '{app_id}'"
103
+ logger.error(message)
104
+ raise ValueError(message)
105
+
106
+ auth_types = {e.value for e in ConnectionType}
107
+ if expected_schema not in auth_types:
108
+ message = f"The expected type '{expected_schema}' cannot be resolved into a valid connection auth type ({', '.join(list(auth_types))})"
109
+ logger.error(message)
110
+ raise ValueError(message)
111
+
112
+ return expected_schema
113
+
114
+ def get_application_connection_credentials(type: ConnectionType, app_id: str) -> CREDENTIALS:
115
+ sanitized_app_id = sanatize_app_id(app_id=app_id)
116
+ expected_schema = get_connection_type(app_id=app_id)
117
+
118
+ if not _validate_schema_type(requested_type=type, expected_type=expected_schema):
119
+ message = f"The requested type '{type.__name__}' does not match the type '{expected_schema}' for the connection '{app_id}'"
120
+ logger.error(message)
121
+ raise ValueError(message)
122
+
123
+ return _get_credentials_model(connection_type=type, app_id=sanitized_app_id)