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.
- ibm_watsonx_orchestrate/__init__.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/__init__.py +0 -0
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +5 -0
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +28 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +204 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/connections/connections.py +123 -0
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +260 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +27 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +59 -0
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +243 -0
- ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +4 -0
- ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +36 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +332 -0
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +195 -0
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +162 -0
- ibm_watsonx_orchestrate/agent_builder/utils/__init__.py +0 -0
- ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +149 -0
- ibm_watsonx_orchestrate/cli/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +192 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +660 -0
- ibm_watsonx_orchestrate/cli/commands/channels/channels_command.py +15 -0
- ibm_watsonx_orchestrate/cli/commands/channels/channels_controller.py +16 -0
- ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -0
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_command.py +32 -0
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +141 -0
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +43 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +307 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +517 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +78 -0
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +189 -0
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +9 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +79 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +201 -0
- ibm_watsonx_orchestrate/cli/commands/login/login_command.py +17 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +128 -0
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +623 -0
- ibm_watsonx_orchestrate/cli/commands/settings/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/__init__.py +0 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/langfuse_command.py +175 -0
- ibm_watsonx_orchestrate/cli/commands/settings/observability/observability_command.py +11 -0
- ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py +10 -0
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +85 -0
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +564 -0
- ibm_watsonx_orchestrate/cli/commands/tools/types.py +10 -0
- ibm_watsonx_orchestrate/cli/config.py +226 -0
- ibm_watsonx_orchestrate/cli/main.py +32 -0
- ibm_watsonx_orchestrate/client/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +46 -0
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +38 -0
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +38 -0
- ibm_watsonx_orchestrate/client/analytics/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/analytics/llm/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +50 -0
- ibm_watsonx_orchestrate/client/base_api_client.py +113 -0
- ibm_watsonx_orchestrate/client/base_service_instance.py +10 -0
- ibm_watsonx_orchestrate/client/client.py +71 -0
- ibm_watsonx_orchestrate/client/client_errors.py +359 -0
- ibm_watsonx_orchestrate/client/connections/__init__.py +10 -0
- ibm_watsonx_orchestrate/client/connections/connections_client.py +162 -0
- ibm_watsonx_orchestrate/client/connections/utils.py +27 -0
- ibm_watsonx_orchestrate/client/credentials.py +123 -0
- ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +46 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +91 -0
- ibm_watsonx_orchestrate/client/service_instance.py +73 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +41 -0
- ibm_watsonx_orchestrate/client/utils.py +95 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +595 -0
- ibm_watsonx_orchestrate/docker/default.env +125 -0
- ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl +0 -0
- ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz +0 -0
- ibm_watsonx_orchestrate/docker/start-up.sh +61 -0
- ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -0
- ibm_watsonx_orchestrate/run/__init__.py +0 -0
- ibm_watsonx_orchestrate/run/connections.py +40 -0
- ibm_watsonx_orchestrate/utils/__init__.py +0 -0
- ibm_watsonx_orchestrate/utils/logging/__init__.py +0 -0
- ibm_watsonx_orchestrate/utils/logging/logger.py +26 -0
- ibm_watsonx_orchestrate/utils/logging/logging.yaml +18 -0
- ibm_watsonx_orchestrate/utils/utils.py +15 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/METADATA +34 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/RECORD +89 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/WHEEL +4 -0
- ibm_watsonx_orchestrate-1.0.0.dist-info/entry_points.txt +2 -0
- 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)
|