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,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
@@ -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,10 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class BaseServiceInstance(ABC):
5
+ def __init__(self):
6
+ pass
7
+
8
+ @abstractmethod
9
+ def _create_token(self) -> str:
10
+ raise NotImplementedError("_create_token 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)