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,226 @@
|
|
1
|
+
import os
|
2
|
+
import logging
|
3
|
+
import yaml
|
4
|
+
from copy import deepcopy
|
5
|
+
|
6
|
+
from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
|
7
|
+
from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
|
8
|
+
from enum import Enum
|
9
|
+
|
10
|
+
# Section Headers
|
11
|
+
AUTH_SECTION_HEADER = "auth"
|
12
|
+
CONTEXT_SECTION_HEADER = "context"
|
13
|
+
ENVIRONMENTS_SECTION_HEADER = "environments"
|
14
|
+
PYTHON_REGISTRY_HEADER = "python_registry"
|
15
|
+
|
16
|
+
# Option Names
|
17
|
+
AUTH_MCSP_API_KEY_OPT = "wxo_mcsp_api_key"
|
18
|
+
AUTH_MCSP_TOKEN_OPT = "wxo_mcsp_token"
|
19
|
+
AUTH_MCSP_TOKEN_EXPIRY_OPT = "wxo_mcsp_token_expiry"
|
20
|
+
CONTEXT_ACTIVE_ENV_OPT = "active_environment"
|
21
|
+
PYTHON_REGISTRY_TYPE_OPT = "type"
|
22
|
+
PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT = "test_package_version_override"
|
23
|
+
ENV_WXO_URL_OPT = "wxo_url"
|
24
|
+
ENV_IAM_URL_OPT = "iam_url"
|
25
|
+
PROTECTED_ENV_NAME = "local"
|
26
|
+
ENV_AUTH_TYPE = "auth_type"
|
27
|
+
|
28
|
+
DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
|
29
|
+
CHAT_UI_PORT = "3000"
|
30
|
+
|
31
|
+
DEFAULT_CONFIG_FILE_FOLDER = f"{os.path.expanduser('~')}/.config/orchestrate"
|
32
|
+
DEFAULT_CONFIG_FILE = "config.yaml"
|
33
|
+
DEFAULT_CONFIG_FILE_CONTENT = {
|
34
|
+
CONTEXT_SECTION_HEADER: {CONTEXT_ACTIVE_ENV_OPT: None},
|
35
|
+
PYTHON_REGISTRY_HEADER: {
|
36
|
+
PYTHON_REGISTRY_TYPE_OPT: str(RegistryType.TESTPYPI),
|
37
|
+
PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT: None
|
38
|
+
},
|
39
|
+
ENVIRONMENTS_SECTION_HEADER: {
|
40
|
+
PROTECTED_ENV_NAME: {
|
41
|
+
ENV_WXO_URL_OPT: DEFAULT_LOCAL_SERVICE_URL
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
AUTH_CONFIG_FILE_FOLDER = f"{os.path.expanduser('~')}/.cache/orchestrate"
|
47
|
+
AUTH_CONFIG_FILE = "credentials.yaml"
|
48
|
+
AUTH_CONFIG_FILE_CONTENT = {
|
49
|
+
AUTH_SECTION_HEADER: {
|
50
|
+
PROTECTED_ENV_NAME: None
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
logger = logging.getLogger(__name__)
|
55
|
+
|
56
|
+
|
57
|
+
def merge_configs(source: dict, destination: dict) -> dict:
|
58
|
+
if source:
|
59
|
+
merged_object = deepcopy(source)
|
60
|
+
else:
|
61
|
+
merged_object = {}
|
62
|
+
|
63
|
+
for key, value in destination.items():
|
64
|
+
if isinstance(value, dict):
|
65
|
+
node = merged_object.setdefault(key, {})
|
66
|
+
merged_object[key] = merge_configs(node, value)
|
67
|
+
else:
|
68
|
+
merged_object[key] = value
|
69
|
+
return merged_object
|
70
|
+
|
71
|
+
|
72
|
+
def _check_if_default_config_file(folder, file):
|
73
|
+
return folder == DEFAULT_CONFIG_FILE_FOLDER and file == DEFAULT_CONFIG_FILE
|
74
|
+
|
75
|
+
|
76
|
+
def _check_if_auth_config_file(folder, file):
|
77
|
+
return folder == AUTH_CONFIG_FILE_FOLDER and file == AUTH_CONFIG_FILE
|
78
|
+
|
79
|
+
|
80
|
+
def clear_protected_env_credentials_token():
|
81
|
+
auth_cfg = Config(config_file_folder=AUTH_CONFIG_FILE_FOLDER, config_file=AUTH_CONFIG_FILE)
|
82
|
+
auth_cfg.delete(AUTH_SECTION_HEADER, PROTECTED_ENV_NAME, AUTH_MCSP_TOKEN_OPT)
|
83
|
+
|
84
|
+
|
85
|
+
class ConfigFileTypes(str, Enum):
|
86
|
+
AUTH = 'auth'
|
87
|
+
CONFIG = 'config'
|
88
|
+
|
89
|
+
|
90
|
+
class Config:
|
91
|
+
|
92
|
+
def __init__(
|
93
|
+
self,
|
94
|
+
config_file_folder: str = DEFAULT_CONFIG_FILE_FOLDER,
|
95
|
+
config_file: str = DEFAULT_CONFIG_FILE,
|
96
|
+
):
|
97
|
+
self.config_file_folder = config_file_folder
|
98
|
+
self.config_file = config_file
|
99
|
+
self.config_file_path = os.path.join(self.config_file_folder, self.config_file)
|
100
|
+
self.file_type = None
|
101
|
+
|
102
|
+
if _check_if_default_config_file(folder=self.config_file_folder, file=self.config_file):
|
103
|
+
self.file_type = ConfigFileTypes.CONFIG
|
104
|
+
elif _check_if_auth_config_file(folder=self.config_file_folder, file=self.config_file):
|
105
|
+
self.file_type = ConfigFileTypes.AUTH
|
106
|
+
|
107
|
+
# Check if config file already exists
|
108
|
+
if not os.path.exists(self.config_file_path):
|
109
|
+
self.create_config_file()
|
110
|
+
|
111
|
+
# Check if file has defaults
|
112
|
+
with open(self.config_file_path, 'r') as conf_file:
|
113
|
+
config_data = yaml_safe_load(conf_file) or {}
|
114
|
+
if self.file_type == ConfigFileTypes.CONFIG:
|
115
|
+
if not config_data.get(ENVIRONMENTS_SECTION_HEADER, {}).get(PROTECTED_ENV_NAME, False):
|
116
|
+
logger.debug("Setting default config data")
|
117
|
+
self.create_defaults(DEFAULT_CONFIG_FILE_CONTENT)
|
118
|
+
|
119
|
+
if not config_data.get(PYTHON_REGISTRY_HEADER, {}).get(PYTHON_REGISTRY_TYPE_OPT, False):
|
120
|
+
self.create_defaults({
|
121
|
+
PYTHON_REGISTRY_HEADER: DEFAULT_CONFIG_FILE_CONTENT.get(PYTHON_REGISTRY_HEADER, {})
|
122
|
+
})
|
123
|
+
|
124
|
+
elif self.file_type == ConfigFileTypes.AUTH:
|
125
|
+
if PROTECTED_ENV_NAME not in set(config_data.get(AUTH_SECTION_HEADER, {}).keys()):
|
126
|
+
logger.debug("Setting default credentials data")
|
127
|
+
self.create_defaults(AUTH_CONFIG_FILE_CONTENT)
|
128
|
+
|
129
|
+
def create_config_file(self) -> None:
|
130
|
+
logger.info(f'Creating config file at location "{self.config_file_path}"')
|
131
|
+
|
132
|
+
os.makedirs(os.path.dirname(self.config_file_path), exist_ok=True)
|
133
|
+
open(self.config_file_path, 'a').close()
|
134
|
+
|
135
|
+
def create_defaults(self, default_content):
|
136
|
+
self.save(default_content)
|
137
|
+
|
138
|
+
def get_active_env(self):
|
139
|
+
return self.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
140
|
+
|
141
|
+
def get_active_env_config(self, option):
|
142
|
+
return self.read(ENVIRONMENTS_SECTION_HEADER, self.get_active_env()).get(option)
|
143
|
+
|
144
|
+
def read(self, section: str, option: str) -> any:
|
145
|
+
try:
|
146
|
+
with open(self.config_file_path, "r") as conf_file:
|
147
|
+
config_data = yaml.load(conf_file, Loader=yaml.SafeLoader)
|
148
|
+
if config_data is None:
|
149
|
+
return None
|
150
|
+
return config_data[section][option]
|
151
|
+
except KeyError:
|
152
|
+
return None
|
153
|
+
|
154
|
+
def write(self, section: str, option: str, value: any) -> None:
|
155
|
+
obj = {section:
|
156
|
+
{option: value}
|
157
|
+
}
|
158
|
+
self.save(obj)
|
159
|
+
|
160
|
+
def save(self, object: dict) -> None:
|
161
|
+
config_data = {}
|
162
|
+
try:
|
163
|
+
with open(self.config_file_path, 'r') as conf_file:
|
164
|
+
config_data = yaml_safe_load(conf_file) or {}
|
165
|
+
except FileNotFoundError:
|
166
|
+
pass
|
167
|
+
|
168
|
+
config_data = merge_configs(config_data, object)
|
169
|
+
|
170
|
+
with open(self.config_file_path, 'w') as conf_file:
|
171
|
+
yaml.dump(config_data, conf_file, allow_unicode=True)
|
172
|
+
|
173
|
+
def get(self, *args):
|
174
|
+
"""
|
175
|
+
Accesses an item of arbitrary depth from the config file.
|
176
|
+
Takes an arbitrary number of args. Uses the args in order
|
177
|
+
as keys to access deeper sections of the config and then returning the last specified key.
|
178
|
+
"""
|
179
|
+
|
180
|
+
config_data = {}
|
181
|
+
try:
|
182
|
+
with open(self.config_file_path, 'r') as conf_file:
|
183
|
+
config_data = yaml_safe_load(conf_file) or {}
|
184
|
+
except FileNotFoundError:
|
185
|
+
pass
|
186
|
+
|
187
|
+
if len(args) < 1:
|
188
|
+
return config_data
|
189
|
+
|
190
|
+
try:
|
191
|
+
nested_dict = config_data
|
192
|
+
for key in args[:-1]:
|
193
|
+
nested_dict = nested_dict[key]
|
194
|
+
|
195
|
+
return nested_dict[args[-1]]
|
196
|
+
except KeyError as e:
|
197
|
+
raise KeyError(f"Failed to get data from config. Key {e} not in {list(nested_dict.keys())}")
|
198
|
+
|
199
|
+
def delete(self, *args) -> None:
|
200
|
+
"""
|
201
|
+
Deletes an item of arbitrary depth from the config file.
|
202
|
+
Takes an arbitrary number of args. Uses the args in order
|
203
|
+
as keys to access deeper sections of the config and then deleting the last specified key.
|
204
|
+
"""
|
205
|
+
if len(args) < 1:
|
206
|
+
raise ValueError("Config.delete() requires at least one positional argument")
|
207
|
+
|
208
|
+
config_data = {}
|
209
|
+
try:
|
210
|
+
with open(self.config_file_path, 'r') as conf_file:
|
211
|
+
config_data = yaml_safe_load(conf_file) or {}
|
212
|
+
except FileNotFoundError:
|
213
|
+
pass
|
214
|
+
|
215
|
+
try:
|
216
|
+
deletion_data = deepcopy(config_data)
|
217
|
+
nested_dict = deletion_data
|
218
|
+
for key in args[:-1]:
|
219
|
+
nested_dict = nested_dict[key]
|
220
|
+
|
221
|
+
del (nested_dict[args[-1]])
|
222
|
+
except KeyError as e:
|
223
|
+
raise KeyError(f"Failed to delete from config. Key {e} not in {list(nested_dict.keys())}")
|
224
|
+
|
225
|
+
with open(self.config_file_path, 'w') as conf_file:
|
226
|
+
yaml.dump(deletion_data, conf_file, allow_unicode=True)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import typer
|
2
|
+
|
3
|
+
from ibm_watsonx_orchestrate.cli.commands.connections.connections_command import connections_app
|
4
|
+
from ibm_watsonx_orchestrate.cli.commands.login.login_command import login_app
|
5
|
+
from ibm_watsonx_orchestrate.cli.commands.settings.settings_command import settings_app
|
6
|
+
from ibm_watsonx_orchestrate.cli.commands.tools.tools_command import tools_app
|
7
|
+
from ibm_watsonx_orchestrate.cli.commands.agents.agents_command import agents_app
|
8
|
+
from ibm_watsonx_orchestrate.cli.commands.server.server_command import server_app
|
9
|
+
from ibm_watsonx_orchestrate.cli.commands.chat.chat_command import chat_app
|
10
|
+
from ibm_watsonx_orchestrate.cli.commands.models.models_command import models_app
|
11
|
+
from ibm_watsonx_orchestrate.cli.commands.environment.environment_command import environment_app
|
12
|
+
from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import channel_app
|
13
|
+
from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
|
14
|
+
|
15
|
+
app = typer.Typer(
|
16
|
+
no_args_is_help=True,
|
17
|
+
pretty_exceptions_enable=False
|
18
|
+
)
|
19
|
+
app.add_typer(login_app)
|
20
|
+
app.add_typer(environment_app, name="env", help='Add, remove, or select the activate env other commands will interact with (either your local server or a production instance)')
|
21
|
+
app.add_typer(tools_app, name="tools", help='Interact with the tools in your active env')
|
22
|
+
app.add_typer(agents_app, name="agents", help='Interact with the agents in your active env')
|
23
|
+
app.add_typer(connections_app, name="connections", help='Interact with the agents in your active env')
|
24
|
+
app.add_typer(server_app, name="server", help='Manipulate your local wxo lite server [requires docker pull credentials]')
|
25
|
+
app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local list wxo lite server [requires docker pull credentials]')
|
26
|
+
app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
|
27
|
+
app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
|
28
|
+
app.add_typer(channel_app, name="channels")
|
29
|
+
app.add_typer(knowledge_bases_app, name="knowledge-bases")
|
30
|
+
|
31
|
+
if __name__ == "__main__":
|
32
|
+
app()
|
File without changes
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
2
|
+
from typing_extensions import List
|
3
|
+
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
4
|
+
|
5
|
+
|
6
|
+
class AgentClient(BaseAPIClient):
|
7
|
+
"""
|
8
|
+
Client to handle CRUD operations for Native Agent endpoint
|
9
|
+
"""
|
10
|
+
def __init__(self, *args, **kwargs):
|
11
|
+
super().__init__(*args, **kwargs)
|
12
|
+
self.base_endpoint = "/orchestrate/agents" if is_local_dev(self.base_url) else "/agents"
|
13
|
+
|
14
|
+
|
15
|
+
def create(self, payload: dict) -> dict:
|
16
|
+
return self._post(self.base_endpoint, data=payload)
|
17
|
+
|
18
|
+
def get(self) -> dict:
|
19
|
+
return self._get(self.base_endpoint)
|
20
|
+
|
21
|
+
def update(self, agent_id: str, data: dict) -> dict:
|
22
|
+
return self._patch(f"{self.base_endpoint}/{agent_id}", data=data)
|
23
|
+
|
24
|
+
def delete(self, agent_id: str) -> dict:
|
25
|
+
return self._delete(f"{self.base_endpoint}/{agent_id}")
|
26
|
+
|
27
|
+
def get_draft_by_name(self, agent_name: str) -> List[dict]:
|
28
|
+
return self.get_drafts_by_names([agent_name])
|
29
|
+
|
30
|
+
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
31
|
+
formatted_agent_names = [f"names={x}" for x in agent_names]
|
32
|
+
return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}")
|
33
|
+
|
34
|
+
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
35
|
+
if agent_id is None:
|
36
|
+
return ""
|
37
|
+
else:
|
38
|
+
try:
|
39
|
+
agent = self._get(f"{self.base_endpoint}/{agent_id}")
|
40
|
+
return agent
|
41
|
+
except ClientAPIException as e:
|
42
|
+
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
43
|
+
return ""
|
44
|
+
raise(e)
|
45
|
+
|
46
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
2
|
+
from typing_extensions import List
|
3
|
+
|
4
|
+
|
5
|
+
class AssistantAgentClient(BaseAPIClient):
|
6
|
+
"""
|
7
|
+
Client to handle CRUD operations for Assistant Agent endpoint
|
8
|
+
"""
|
9
|
+
def create(self, payload: dict) -> dict:
|
10
|
+
return self._post("/assistants/watsonx", data=payload)
|
11
|
+
|
12
|
+
def get(self) -> dict:
|
13
|
+
return self._get("/assistants/watsonx")
|
14
|
+
|
15
|
+
def update(self, agent_id: str, data: dict) -> dict:
|
16
|
+
return self._patch(f"/assistants/watsonx/{agent_id}", data=data)
|
17
|
+
|
18
|
+
def delete(self, agent_id: str) -> dict:
|
19
|
+
return self._delete(f"/assistants/watsonx/{agent_id}")
|
20
|
+
|
21
|
+
def get_draft_by_name(self, agent_name: str) -> List[dict]:
|
22
|
+
return self.get_drafts_by_names([agent_name])
|
23
|
+
|
24
|
+
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
25
|
+
formatted_agent_names = [f"names={x}" for x in agent_names]
|
26
|
+
return self._get(f"/assistants/watsonx?{'&'.join(formatted_agent_names)}")
|
27
|
+
|
28
|
+
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
29
|
+
if agent_id is None:
|
30
|
+
return ""
|
31
|
+
else:
|
32
|
+
try:
|
33
|
+
agent = self._get(f"/assistants/watsonx/{agent_id}")
|
34
|
+
return agent
|
35
|
+
except ClientAPIException as e:
|
36
|
+
if e.response.status_code == 404 and "Assistant not found" in e.response.text:
|
37
|
+
return ""
|
38
|
+
raise(e)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
|
2
|
+
from typing_extensions import List
|
3
|
+
|
4
|
+
class ExternalAgentClient(BaseAPIClient):
|
5
|
+
"""
|
6
|
+
Client to handle CRUD operations for External Agent endpoint
|
7
|
+
"""
|
8
|
+
|
9
|
+
def create(self, payload: dict) -> dict:
|
10
|
+
return self._post("/agents/external-chat", data=payload)
|
11
|
+
|
12
|
+
def get(self) -> dict:
|
13
|
+
return self._get("/agents/external-chat")
|
14
|
+
|
15
|
+
def update(self, agent_id: str, data: dict) -> dict:
|
16
|
+
return self._patch(f"/agents/external-chat/{agent_id}", data=data)
|
17
|
+
|
18
|
+
def delete(self, agent_id: str) -> dict:
|
19
|
+
return self._delete(f"/agents/external-chat/{agent_id}")
|
20
|
+
|
21
|
+
def get_draft_by_name(self, agent_name: str) -> List[dict]:
|
22
|
+
return self.get_drafts_by_names([agent_name])
|
23
|
+
|
24
|
+
def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
|
25
|
+
formatted_agent_names = [f"names={x}" for x in agent_names]
|
26
|
+
return self._get(f"/agents/external-chat?{'&'.join(formatted_agent_names)}&include_hidden=true")
|
27
|
+
|
28
|
+
def get_draft_by_id(self, agent_id: str) -> List[dict]:
|
29
|
+
if agent_id is None:
|
30
|
+
return ""
|
31
|
+
else:
|
32
|
+
try:
|
33
|
+
agent = self._get(f"/agents/external-chat/{agent_id}")
|
34
|
+
return agent
|
35
|
+
except ClientAPIException as e:
|
36
|
+
if e.response.status_code == 404 and "not found with the given name" in e.response.text:
|
37
|
+
return ""
|
38
|
+
raise(e)
|
File without changes
|
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import logging
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
6
|
+
|
7
|
+
from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
class AnalyticsLLMUpsertToolIdentifier(str, Enum):
|
13
|
+
LANGFUSE = 'langfuse'
|
14
|
+
|
15
|
+
|
16
|
+
class AnalyticsLLMResponse(BaseModel):
|
17
|
+
status: str
|
18
|
+
|
19
|
+
|
20
|
+
class AnalyticsLLMConfig(BaseModel):
|
21
|
+
model_config = ConfigDict(extra='allow')
|
22
|
+
project_id: str = 'default'
|
23
|
+
host_uri: str
|
24
|
+
tool_identifier: AnalyticsLLMUpsertToolIdentifier = AnalyticsLLMUpsertToolIdentifier.LANGFUSE
|
25
|
+
mask_pii: bool = False
|
26
|
+
config_json: dict
|
27
|
+
|
28
|
+
class AnalyticsLLMClient(BaseAPIClient):
|
29
|
+
"""
|
30
|
+
Client to handle CRUD operations for Analytics LLM Endpoint
|
31
|
+
"""
|
32
|
+
def create(self):
|
33
|
+
raise RuntimeError('unimplemented')
|
34
|
+
|
35
|
+
def get(self, project_id: Optional[str] = None) -> AnalyticsLLMConfig:
|
36
|
+
params = {}
|
37
|
+
if project_id:
|
38
|
+
params['project_id'] = project_id
|
39
|
+
|
40
|
+
response = self._get(f"/analytics/llm", params=params)
|
41
|
+
|
42
|
+
return AnalyticsLLMConfig.model_validate(response)
|
43
|
+
|
44
|
+
def update(self, request: AnalyticsLLMConfig) -> AnalyticsLLMResponse:
|
45
|
+
response = self._put('/analytics/llm', request.model_dump())
|
46
|
+
return AnalyticsLLMResponse.model_validate(response)
|
47
|
+
|
48
|
+
def delete(self) -> AnalyticsLLMResponse:
|
49
|
+
response = self._delete('/analytics/llm')
|
50
|
+
return AnalyticsLLMResponse.model_validate(response)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
import requests
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
|
6
|
+
|
7
|
+
|
8
|
+
class ClientAPIException(requests.HTTPError):
|
9
|
+
|
10
|
+
def __init__(self, *args, request=..., response=...):
|
11
|
+
super().__init__(*args, request=request, response=response)
|
12
|
+
|
13
|
+
def __repr__(self):
|
14
|
+
status = self.response.status_code
|
15
|
+
resp = self.response.text
|
16
|
+
try:
|
17
|
+
resp = json.dumps(resp).get('detail', None)
|
18
|
+
except:
|
19
|
+
pass
|
20
|
+
return f"ClientAPIException(status_code={status}, message={resp})"
|
21
|
+
|
22
|
+
def __str__(self):
|
23
|
+
return self.__repr__()
|
24
|
+
|
25
|
+
|
26
|
+
class BaseAPIClient:
|
27
|
+
|
28
|
+
def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, authenticator: MCSPAuthenticator = None):
|
29
|
+
self.base_url = base_url.rstrip("/") # remove trailing slash
|
30
|
+
self.api_key = api_key
|
31
|
+
self.authenticator = authenticator
|
32
|
+
|
33
|
+
# api path can be re-written by api proxy when deployed
|
34
|
+
# TO-DO: re-visit this when shipping to production
|
35
|
+
self.is_local = is_local
|
36
|
+
|
37
|
+
if not self.is_local:
|
38
|
+
self.base_url = f"{self.base_url}/v1/orchestrate"
|
39
|
+
|
40
|
+
def _get_headers(self) -> dict:
|
41
|
+
headers = {}
|
42
|
+
if self.api_key:
|
43
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
44
|
+
elif self.authenticator:
|
45
|
+
headers["Authorization"] = f"Bearer {self.authenticator.token_manager.get_token()}"
|
46
|
+
return headers
|
47
|
+
|
48
|
+
def _get(self, path: str, params: dict = None, data=None) -> dict:
|
49
|
+
|
50
|
+
url = f"{self.base_url}{path}"
|
51
|
+
response = requests.get(url, headers=self._get_headers(), params=params, data=data)
|
52
|
+
self._check_response(response)
|
53
|
+
return response.json()
|
54
|
+
|
55
|
+
def _post(self, path: str, data: dict = None, files: dict = None) -> dict:
|
56
|
+
url = f"{self.base_url}{path}"
|
57
|
+
response = requests.post(url, headers=self._get_headers(), json=data, files=files)
|
58
|
+
self._check_response(response)
|
59
|
+
return response.json() if response.text else {}
|
60
|
+
|
61
|
+
def _post_form_data(self, path: str, data: dict = None, files: dict = None) -> dict:
|
62
|
+
url = f"{self.base_url}{path}"
|
63
|
+
# Use data argument instead of json so data is encoded as application/x-www-form-urlencoded
|
64
|
+
response = requests.post(url, headers=self._get_headers(), data=data, files=files)
|
65
|
+
self._check_response(response)
|
66
|
+
return response.json() if response.text else {}
|
67
|
+
|
68
|
+
def _put(self, path: str, data: dict = None) -> dict:
|
69
|
+
|
70
|
+
url = f"{self.base_url}{path}"
|
71
|
+
response = requests.put(url, headers=self._get_headers(), json=data)
|
72
|
+
self._check_response(response)
|
73
|
+
return response.json() if response.text else {}
|
74
|
+
|
75
|
+
def _patch(self, path: str, data: dict = None) -> dict:
|
76
|
+
url = f"{self.base_url}{path}"
|
77
|
+
response = requests.patch(url, headers=self._get_headers(), json=data)
|
78
|
+
self._check_response(response)
|
79
|
+
return response.json() if response.text else {}
|
80
|
+
|
81
|
+
def _patch_form_data(self, path: str, data: dict = None, files = None) -> dict:
|
82
|
+
url = f"{self.base_url}{path}"
|
83
|
+
response = requests.patch(url, headers=self._get_headers(), data=data, files=files)
|
84
|
+
self._check_response(response)
|
85
|
+
return response.json() if response.text else {}
|
86
|
+
|
87
|
+
def _delete(self, path: str, data=None) -> dict:
|
88
|
+
url = f"{self.base_url}{path}"
|
89
|
+
response = requests.delete(url, headers=self._get_headers(), json=data)
|
90
|
+
self._check_response(response)
|
91
|
+
return response.json() if response.text else {}
|
92
|
+
|
93
|
+
def _check_response(self, response: requests.Response):
|
94
|
+
try:
|
95
|
+
response.raise_for_status()
|
96
|
+
except requests.HTTPError as e:
|
97
|
+
raise ClientAPIException(request=e.request, response=e.response)
|
98
|
+
|
99
|
+
@abstractmethod
|
100
|
+
def create(self, *args, **kwargs):
|
101
|
+
raise NotImplementedError("create method of the client must be implemented")
|
102
|
+
|
103
|
+
@abstractmethod
|
104
|
+
def delete(self, *args, **kwargs):
|
105
|
+
raise NotImplementedError("delete method of the client must be implemented")
|
106
|
+
|
107
|
+
@abstractmethod
|
108
|
+
def update(self, *args, **kwargs):
|
109
|
+
raise NotImplementedError("update method of the client must be implemented")
|
110
|
+
|
111
|
+
@abstractmethod
|
112
|
+
def get(self, *args, **kwargs):
|
113
|
+
raise NotImplementedError("get method of the client must be implemented")
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import copy
|
2
|
+
import logging
|
3
|
+
from typing import Any, cast
|
4
|
+
|
5
|
+
from ibm_watsonx_orchestrate.client.client_errors import NoCredentialsProvided, ClientError
|
6
|
+
from ibm_watsonx_orchestrate.client.credentials import Credentials
|
7
|
+
from ibm_watsonx_orchestrate.client.service_instance import ServiceInstance
|
8
|
+
from ibm_watsonx_orchestrate.client.local_service_instance import LocalServiceInstance
|
9
|
+
from ibm_watsonx_orchestrate.client.utils import is_local_dev
|
10
|
+
|
11
|
+
|
12
|
+
class Client:
|
13
|
+
"""The main class of ibm_watsonx_orchestrate. The very heart of the module. Client contains objects that manage the service reasources.
|
14
|
+
|
15
|
+
To explore how to use Client, refer to:
|
16
|
+
- :ref:`Setup<setup>` - to check correct initialization of Client for a specific environment.
|
17
|
+
- :ref:`Core<core>` - to explore core properties of an Client object.
|
18
|
+
|
19
|
+
:param url: URL of the service
|
20
|
+
:type url: str
|
21
|
+
|
22
|
+
:param credentials: credentials used to connect with the service
|
23
|
+
:type credentials: Credentials
|
24
|
+
|
25
|
+
**Example**
|
26
|
+
|
27
|
+
.. code-block:: python
|
28
|
+
|
29
|
+
from ibm_watsonx_orchestrate import Client, Credentials
|
30
|
+
|
31
|
+
credentials = Credentials(
|
32
|
+
url = "<url>",
|
33
|
+
api_key = "<api_key>"
|
34
|
+
)
|
35
|
+
|
36
|
+
client = Client(credentials, space_id="<space_id>")
|
37
|
+
|
38
|
+
client.models.list()
|
39
|
+
client.deployments.get_details()
|
40
|
+
|
41
|
+
client.set.default_project("<project_id>")
|
42
|
+
|
43
|
+
...
|
44
|
+
|
45
|
+
"""
|
46
|
+
|
47
|
+
def __init__(
|
48
|
+
self,
|
49
|
+
credentials: Credentials | None = None,
|
50
|
+
**kwargs: Any,
|
51
|
+
) -> None:
|
52
|
+
if credentials is None:
|
53
|
+
raise TypeError("Client() missing 1 required argument: 'credentials'")
|
54
|
+
|
55
|
+
self.credentials = copy.deepcopy(credentials)
|
56
|
+
|
57
|
+
self.token: str | None = None
|
58
|
+
if credentials is None:
|
59
|
+
raise NoCredentialsProvided()
|
60
|
+
if self.credentials.url is None:
|
61
|
+
raise ClientError("No URL Provided")
|
62
|
+
if not self.credentials.url.startswith("https://"):
|
63
|
+
if not is_local_dev(self.credentials.url):
|
64
|
+
raise ClientError("Invalid URL Format. URL must start stil 'https://'")
|
65
|
+
if self.credentials.url[-1] == "/":
|
66
|
+
self.credentials.url = self.credentials.url.rstrip("/")
|
67
|
+
|
68
|
+
if not is_local_dev(self.credentials.url):
|
69
|
+
self.service_instance: ServiceInstance = ServiceInstance(self)
|
70
|
+
else:
|
71
|
+
self.service_instance: LocalServiceInstance = LocalServiceInstance(self)
|