ibm-watsonx-orchestrate 1.7.0a0__py3-none-any.whl → 1.7.0b1__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 (50) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +3 -3
  3. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +3 -2
  4. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +3 -2
  5. ibm_watsonx_orchestrate/agent_builder/agents/types.py +9 -8
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +1 -0
  7. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +25 -10
  8. ibm_watsonx_orchestrate/agent_builder/connections/types.py +5 -9
  9. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +1 -22
  10. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +1 -17
  11. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +2 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
  13. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +136 -92
  14. ibm_watsonx_orchestrate/agent_builder/tools/types.py +10 -9
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +7 -7
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +35 -7
  17. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
  18. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +6 -4
  19. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -6
  20. ibm_watsonx_orchestrate/cli/commands/environment/types.py +2 -0
  21. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +118 -30
  22. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +22 -9
  23. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +0 -18
  24. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +33 -19
  25. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +1 -1
  26. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +64 -9
  27. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +1 -1
  28. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +93 -14
  29. ibm_watsonx_orchestrate/cli/config.py +3 -3
  30. ibm_watsonx_orchestrate/cli/init_helper.py +10 -1
  31. ibm_watsonx_orchestrate/cli/main.py +2 -0
  32. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +1 -1
  33. ibm_watsonx_orchestrate/client/local_service_instance.py +3 -1
  34. ibm_watsonx_orchestrate/client/service_instance.py +33 -7
  35. ibm_watsonx_orchestrate/docker/compose-lite.yml +6 -4
  36. ibm_watsonx_orchestrate/docker/default.env +22 -15
  37. ibm_watsonx_orchestrate/flow_builder/flows/__init__.py +3 -1
  38. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +4 -2
  39. ibm_watsonx_orchestrate/flow_builder/flows/events.py +10 -9
  40. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +91 -20
  41. ibm_watsonx_orchestrate/flow_builder/node.py +12 -1
  42. ibm_watsonx_orchestrate/flow_builder/types.py +169 -16
  43. ibm_watsonx_orchestrate/flow_builder/utils.py +121 -6
  44. ibm_watsonx_orchestrate/utils/exceptions.py +23 -0
  45. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.7.0b1.dist-info}/METADATA +5 -5
  46. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.7.0b1.dist-info}/RECORD +49 -49
  47. ibm_watsonx_orchestrate/flow_builder/resources/flow_status.openapi.yml +0 -66
  48. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.7.0b1.dist-info}/WHEEL +0 -0
  49. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.7.0b1.dist-info}/entry_points.txt +0 -0
  50. {ibm_watsonx_orchestrate-1.7.0a0.dist-info → ibm_watsonx_orchestrate-1.7.0b1.dist-info}/licenses/LICENSE +0 -0
@@ -5,9 +5,6 @@ import os
5
5
  from typing import Any, Callable, Dict, List, get_type_hints
6
6
  import logging
7
7
 
8
- import docstring_parser
9
- from langchain_core.tools.base import create_schema_from_function
10
- from langchain_core.utils.json_schema import dereference_refs
11
8
  from pydantic import TypeAdapter, BaseModel
12
9
 
13
10
  from ibm_watsonx_orchestrate.utils.utils import yaml_safe_load
@@ -15,6 +12,7 @@ from ibm_watsonx_orchestrate.agent_builder.connections import ExpectedCredential
15
12
  from .base_tool import BaseTool
16
13
  from .types import PythonToolKind, ToolSpec, ToolPermission, ToolRequestBody, ToolResponseBody, JsonSchemaObject, ToolBinding, \
17
14
  PythonToolBinding
15
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
18
16
 
19
17
  _all_tools = []
20
18
  logger = logging.getLogger(__name__)
@@ -25,15 +23,136 @@ JOIN_TOOL_PARAMS = {
25
23
  'messages': List[Dict[str, Any]],
26
24
  }
27
25
 
26
+ def _parse_expected_credentials(expected_credentials: ExpectedCredentials | dict):
27
+ parsed_expected_credentials = []
28
+ if expected_credentials:
29
+ for credential in expected_credentials:
30
+ if isinstance(credential, ExpectedCredentials):
31
+ parsed_expected_credentials.append(credential)
32
+ else:
33
+ parsed_expected_credentials.append(ExpectedCredentials.model_validate(credential))
34
+
35
+ return parsed_expected_credentials
36
+
28
37
  class PythonTool(BaseTool):
29
- def __init__(self, fn, spec: ToolSpec, expected_credentials: List[ExpectedCredentials]=None):
30
- BaseTool.__init__(self, spec=spec)
38
+ def __init__(self,
39
+ fn,
40
+ name: str = None,
41
+ description: str = None,
42
+ input_schema: ToolRequestBody = None,
43
+ output_schema: ToolResponseBody = None,
44
+ permission: ToolPermission = ToolPermission.READ_ONLY,
45
+ expected_credentials: List[ExpectedCredentials] = None,
46
+ display_name: str = None,
47
+ kind: PythonToolKind = PythonToolKind.TOOL,
48
+ spec=None
49
+ ):
31
50
  self.fn = fn
32
- self.expected_credentials=expected_credentials
51
+ self.name = name
52
+ self.description = description
53
+ self.input_schema = input_schema
54
+ self.output_schema = output_schema
55
+ self.permission = permission
56
+ self.display_name = display_name
57
+ self.kind = kind
58
+ self.expected_credentials=_parse_expected_credentials(expected_credentials)
59
+ self._spec = None
60
+ if spec:
61
+ self._spec = spec
33
62
 
34
63
  def __call__(self, *args, **kwargs):
35
64
  return self.fn(*args, **kwargs)
65
+
66
+ @property
67
+ def __tool_spec__(self):
68
+ if self._spec:
69
+ return self._spec
70
+
71
+ import docstring_parser
72
+ from langchain_core.tools.base import create_schema_from_function
73
+ from langchain_core.utils.json_schema import dereference_refs
74
+
75
+ if self.fn.__doc__ is not None:
76
+ doc = docstring_parser.parse(self.fn.__doc__)
77
+ else:
78
+ doc = None
79
+
80
+ _desc = self.description
81
+ if self.description is None and doc is not None:
82
+ _desc = doc.description
83
+
84
+
85
+ spec = ToolSpec(
86
+ name=self.name or self.fn.__name__,
87
+ display_name=self.display_name,
88
+ description=_desc,
89
+ permission=self.permission
90
+ )
91
+
92
+ spec.binding = ToolBinding(python=PythonToolBinding(function=''))
93
+
94
+ linux_friendly_os_cwd = os.getcwd().replace("\\", "/")
95
+ function_binding = (inspect.getsourcefile(self.fn)
96
+ .replace("\\", "/")
97
+ .replace(linux_friendly_os_cwd+'/', '')
98
+ .replace('.py', '')
99
+ .replace('/','.') +
100
+ f":{self.fn.__name__}")
101
+ spec.binding.python.function = function_binding
102
+
103
+ sig = inspect.signature(self.fn)
104
+
105
+ # If the function is a join tool, validate its signature matches the expected parameters. If not, raise error with details.
106
+ if self.kind == PythonToolKind.JOIN_TOOL:
107
+ _validate_join_tool_func(self.fn, sig, spec.name)
108
+
109
+ if not self.input_schema:
110
+ try:
111
+ input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, self.fn, parse_docstring=True)
112
+ except:
113
+ logger.warning("Unable to properly parse parameter descriptions due to incorrectly formatted docstring. This may result in degraded agent performance. To fix this, please ensure the docstring conforms to Google's docstring format.")
114
+ input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, self.fn, parse_docstring=False)
115
+ input_schema_json = input_schema_model.model_json_schema()
116
+ input_schema_json = dereference_refs(input_schema_json)
117
+
118
+ # Convert the input schema to a JsonSchemaObject
119
+ input_schema_obj = JsonSchemaObject(**input_schema_json)
120
+ input_schema_obj = _fix_optional(input_schema_obj)
121
+
122
+ spec.input_schema = ToolRequestBody(
123
+ type='object',
124
+ properties=input_schema_obj.properties or {},
125
+ required=input_schema_obj.required or []
126
+ )
127
+ else:
128
+ spec.input_schema = self.input_schema
129
+
130
+ _validate_input_schema(spec.input_schema)
131
+
132
+ if not self.output_schema:
133
+ ret = sig.return_annotation
134
+ if ret != sig.empty:
135
+ _schema = dereference_refs(TypeAdapter(ret).json_schema())
136
+ if '$defs' in _schema:
137
+ _schema.pop('$defs')
138
+ spec.output_schema = _fix_optional(ToolResponseBody(**_schema))
139
+ else:
140
+ spec.output_schema = ToolResponseBody()
141
+
142
+ if doc is not None and doc.returns is not None and doc.returns.description is not None:
143
+ spec.output_schema.description = doc.returns.description
144
+
145
+ else:
146
+ spec.output_schema = ToolResponseBody()
147
+
148
+ # Validate the generated schema still conforms to the requirement for a join tool
149
+ if self.kind == PythonToolKind.JOIN_TOOL:
150
+ if not spec.is_custom_join_tool():
151
+ raise ValueError(f"Join tool '{spec.name}' does not conform to the expected join tool schema. Please ensure the input schema has the required fields: {JOIN_TOOL_PARAMS.keys()} and the output schema is a string.")
36
152
 
153
+ self._spec = spec
154
+ return spec
155
+
37
156
  @staticmethod
38
157
  def from_spec(file: str) -> 'PythonTool':
39
158
  with open(file, 'r') as f:
@@ -42,10 +161,10 @@ class PythonTool(BaseTool):
42
161
  elif file.endswith('.json'):
43
162
  spec = ToolSpec.model_validate(json.load(f))
44
163
  else:
45
- raise ValueError('file must end in .json, .yaml, or .yml')
164
+ raise BadRequest('file must end in .json, .yaml, or .yml')
46
165
 
47
166
  if spec.binding.python is None:
48
- raise ValueError('failed to load python tool as the tool had no python binding')
167
+ raise BadRequest('failed to load python tool as the tool had no python binding')
49
168
 
50
169
  [module, fn_name] = spec.binding.python.function.split(':')
51
170
  fn = getattr(importlib.import_module(module), fn_name)
@@ -147,92 +266,17 @@ def tool(
147
266
  """
148
267
  # inspiration: https://github.com/pydantic/pydantic/blob/main/pydantic/validate_call_decorator.py
149
268
  def _tool_decorator(fn):
150
- if fn.__doc__ is not None:
151
- doc = docstring_parser.parse(fn.__doc__)
152
- else:
153
- doc = None
154
-
155
- _desc = description
156
- if description is None and doc is not None:
157
- _desc = doc.description
158
-
159
-
160
- spec = ToolSpec(
161
- name=name or fn.__name__,
269
+ t = PythonTool(
270
+ fn=fn,
271
+ name=name,
272
+ description=description,
273
+ input_schema=input_schema,
274
+ output_schema=output_schema,
275
+ permission=permission,
276
+ expected_credentials=expected_credentials,
162
277
  display_name=display_name,
163
- description=_desc,
164
- permission=permission
278
+ kind=kind
165
279
  )
166
-
167
- parsed_expected_credentials = []
168
- if expected_credentials:
169
- for credential in expected_credentials:
170
- if isinstance(credential, ExpectedCredentials):
171
- parsed_expected_credentials.append(credential)
172
- else:
173
- parsed_expected_credentials.append(ExpectedCredentials.model_validate(credential))
174
-
175
- t = PythonTool(fn=fn, spec=spec, expected_credentials=parsed_expected_credentials)
176
- spec.binding = ToolBinding(python=PythonToolBinding(function=''))
177
-
178
- linux_friendly_os_cwd = os.getcwd().replace("\\", "/")
179
- function_binding = (inspect.getsourcefile(fn)
180
- .replace("\\", "/")
181
- .replace(linux_friendly_os_cwd+'/', '')
182
- .replace('.py', '')
183
- .replace('/','.') +
184
- f":{fn.__name__}")
185
- spec.binding.python.function = function_binding
186
-
187
- sig = inspect.signature(fn)
188
-
189
- # If the function is a join tool, validate its signature matches the expected parameters. If not, raise error with details.
190
- if kind == PythonToolKind.JOIN_TOOL:
191
- _validate_join_tool_func(fn, sig, spec.name)
192
-
193
- if not input_schema:
194
- try:
195
- input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, fn, parse_docstring=True)
196
- except:
197
- logger.warning("Unable to properly parse parameter descriptions due to incorrectly formatted docstring. This may result in degraded agent performance. To fix this, please ensure the docstring conforms to Google's docstring format.")
198
- input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, fn, parse_docstring=False)
199
- input_schema_json = input_schema_model.model_json_schema()
200
- input_schema_json = dereference_refs(input_schema_json)
201
-
202
- # Convert the input schema to a JsonSchemaObject
203
- input_schema_obj = JsonSchemaObject(**input_schema_json)
204
- input_schema_obj = _fix_optional(input_schema_obj)
205
-
206
- spec.input_schema = ToolRequestBody(
207
- type='object',
208
- properties=input_schema_obj.properties or {},
209
- required=input_schema_obj.required or []
210
- )
211
- else:
212
- spec.input_schema = input_schema
213
-
214
- _validate_input_schema(spec.input_schema)
215
-
216
- if not output_schema:
217
- ret = sig.return_annotation
218
- if ret != sig.empty:
219
- _schema = dereference_refs(TypeAdapter(ret).json_schema())
220
- if '$defs' in _schema:
221
- _schema.pop('$defs')
222
- spec.output_schema = _fix_optional(ToolResponseBody(**_schema))
223
- else:
224
- spec.output_schema = ToolResponseBody()
225
-
226
- if doc is not None and doc.returns is not None and doc.returns.description is not None:
227
- spec.output_schema.description = doc.returns.description
228
-
229
- else:
230
- spec.output_schema = ToolResponseBody()
231
-
232
- # Validate the generated schema still conforms to the requirement for a join tool
233
- if kind == PythonToolKind.JOIN_TOOL:
234
- if not spec.is_custom_join_tool():
235
- raise ValueError(f"Join tool '{spec.name}' does not conform to the expected join tool schema. Please ensure the input schema has the required fields: {JOIN_TOOL_PARAMS.keys()} and the output schema is a string.")
236
280
 
237
281
  _all_tools.append(t)
238
282
  return t
@@ -2,6 +2,7 @@ from enum import Enum
2
2
  from typing import List, Any, Dict, Literal, Optional, Union
3
3
 
4
4
  from pydantic import BaseModel, model_validator, ConfigDict, Field, AliasChoices
5
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
5
6
 
6
7
 
7
8
  class ToolPermission(str, Enum):
@@ -74,19 +75,19 @@ class OpenApiSecurityScheme(BaseModel):
74
75
  @model_validator(mode='after')
75
76
  def validate_security_scheme(self) -> 'OpenApiSecurityScheme':
76
77
  if self.type == 'http' and self.scheme is None:
77
- raise ValueError("'scheme' is required when type is 'http'")
78
+ raise BadRequest("'scheme' is required when type is 'http'")
78
79
 
79
80
  if self.type == 'oauth2' and self.flows is None:
80
- raise ValueError("'flows' is required when type is 'oauth2'")
81
+ raise BadRequest("'flows' is required when type is 'oauth2'")
81
82
 
82
83
  if self.type == 'openIdConnect' and self.open_id_connect_url is None:
83
- raise ValueError("'open_id_connect_url' is required when type is 'openIdConnect'")
84
+ raise BadRequest("'open_id_connect_url' is required when type is 'openIdConnect'")
84
85
 
85
86
  if self.type == 'apiKey':
86
87
  if self.name is None:
87
- raise ValueError("'name' is required when type is 'apiKey'")
88
+ raise BadRequest("'name' is required when type is 'apiKey'")
88
89
  if self.in_field is None:
89
- raise ValueError("'in_field' is required when type is 'apiKey'")
90
+ raise BadRequest("'in_field' is required when type is 'apiKey'")
90
91
 
91
92
  return self
92
93
 
@@ -111,7 +112,7 @@ class OpenApiToolBinding(BaseModel):
111
112
  @model_validator(mode='after')
112
113
  def validate_openapi_tool_binding(self):
113
114
  if len(self.servers) != 1:
114
- raise ValueError("OpenAPI definition must include exactly one server")
115
+ raise BadRequest("OpenAPI definition must include exactly one server")
115
116
  return self
116
117
 
117
118
 
@@ -129,7 +130,7 @@ class WxFlowsToolBinding(BaseModel):
129
130
  @model_validator(mode='after')
130
131
  def validate_security_scheme(self) -> 'WxFlowsToolBinding':
131
132
  if self.security.type != 'apiKey':
132
- raise ValueError("'security' scheme must be of type 'apiKey'")
133
+ raise BadRequest("'security' scheme must be of type 'apiKey'")
133
134
  return self
134
135
 
135
136
 
@@ -173,9 +174,9 @@ class ToolBinding(BaseModel):
173
174
  self.flow is not None
174
175
  ]
175
176
  if sum(bindings) == 0:
176
- raise ValueError("One binding must be set")
177
+ raise BadRequest("One binding must be set")
177
178
  if sum(bindings) > 1:
178
- raise ValueError("Only one binding can be set")
179
+ raise BadRequest("Only one binding can be set")
179
180
  return self
180
181
 
181
182
  class ToolSpec(BaseModel):
@@ -31,6 +31,13 @@ def agent_create(
31
31
  str,
32
32
  typer.Option("--name", "-n", help="Name of the agent you wish to create"),
33
33
  ],
34
+ description: Annotated[
35
+ str,
36
+ typer.Option(
37
+ "--description",
38
+ help="Description of the agent",
39
+ ),
40
+ ],
34
41
  title: Annotated[
35
42
  str,
36
43
  typer.Option("--title", "-t", help="Title of the agent you wish to create. Only needed for External and Assistant Agents"),
@@ -87,13 +94,6 @@ def agent_create(
87
94
  str,
88
95
  typer.Option("--app-id", help="Application ID for the agent"),
89
96
  ] = None,
90
- description: Annotated[
91
- str,
92
- typer.Option(
93
- "--description",
94
- help="Description of the agent",
95
- ),
96
- ] = None,
97
97
  llm: Annotated[
98
98
  str,
99
99
  typer.Option(
@@ -29,6 +29,7 @@ from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient, Agen
29
29
  from ibm_watsonx_orchestrate.client.agents.external_agent_client import ExternalAgentClient
30
30
  from ibm_watsonx_orchestrate.client.agents.assistant_agent_client import AssistantAgentClient
31
31
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
32
+ from ibm_watsonx_orchestrate.utils.exceptions import BadRequest
32
33
  from ibm_watsonx_orchestrate.client.connections import get_connections_client
33
34
  from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
34
35
 
@@ -71,7 +72,7 @@ def create_agent_from_spec(file:str, kind:str) -> Agent | ExternalAgent | Assist
71
72
  case AgentKind.ASSISTANT:
72
73
  agent = AssistantAgent.from_spec(file)
73
74
  case _:
74
- raise ValueError("'kind' must be either 'native' or 'external'")
75
+ raise BadRequest("'kind' must be either 'native' or 'external'")
75
76
 
76
77
  return agent
77
78
 
@@ -88,7 +89,7 @@ def parse_file(file: str) -> List[Agent | ExternalAgent | AssistantAgent]:
88
89
  agents = import_python_agent(file)
89
90
  return agents
90
91
  else:
91
- raise ValueError("file must end in .json, .yaml, .yml or .py")
92
+ raise BadRequest("file must end in .json, .yaml, .yml or .py")
92
93
 
93
94
  def parse_create_native_args(name: str, kind: AgentKind, description: str | None, **args) -> dict:
94
95
  agent_details = {
@@ -747,14 +748,25 @@ class AgentsController:
747
748
  return knowledge_bases
748
749
 
749
750
  def list_agents(self, kind: AgentKind=None, verbose: bool=False):
751
+ parse_errors = []
752
+
750
753
  if kind == AgentKind.NATIVE or kind is None:
751
754
  response = self.get_native_client().get()
752
- native_agents = [Agent.model_validate(agent) for agent in response]
755
+ native_agents = []
756
+ for agent in response:
757
+ try:
758
+ native_agents.append(Agent.model_validate(agent))
759
+ except Exception as e:
760
+ name = agent.get('name', None)
761
+ parse_errors.append([
762
+ f"Agent '{name}' could not be parsed",
763
+ json.dumps(agent),
764
+ e
765
+ ])
753
766
 
754
767
  if verbose:
755
768
  agents_list = []
756
769
  for agent in native_agents:
757
-
758
770
  agents_list.append(json.loads(agent.dumps_spec()))
759
771
 
760
772
  rich.print(rich.json.JSON(json.dumps(agents_list, indent=4)))
@@ -799,7 +811,13 @@ class AgentsController:
799
811
  if kind == AgentKind.EXTERNAL or kind is None:
800
812
  response = self.get_external_client().get()
801
813
 
802
- external_agents = [ExternalAgent.model_validate(agent) for agent in response]
814
+ external_agents = []
815
+ for agent in response:
816
+ try:
817
+ external_agents.append(ExternalAgent.model_validate(agent))
818
+ except Exception as e:
819
+ name = agent.get('name', None)
820
+ parse_errors.append([f"External Agent {name} could not be parsed", e])
803
821
 
804
822
  response_dict = {agent["id"]: agent for agent in response}
805
823
 
@@ -859,7 +877,13 @@ class AgentsController:
859
877
  if kind == AgentKind.ASSISTANT or kind is None:
860
878
  response = self.get_assistant_client().get()
861
879
 
862
- assistant_agents = [AssistantAgent.model_validate(agent) for agent in response]
880
+ assistant_agents = []
881
+ for agent in response:
882
+ try:
883
+ assistant_agents.append(AssistantAgent.model_validate(agent))
884
+ except Exception as e:
885
+ name = agent.get('name', None)
886
+ parse_errors.append([f"Assistant Agent {name} could not be parsed", e])
863
887
 
864
888
  response_dict = {agent["id"]: agent for agent in response}
865
889
 
@@ -914,6 +938,10 @@ class AgentsController:
914
938
  )
915
939
  rich.print(assistants_table)
916
940
 
941
+ for error in parse_errors:
942
+ for l in error:
943
+ logger.error(l)
944
+
917
945
  def remove_agent(self, name: str, kind: AgentKind):
918
946
  try:
919
947
  if kind == AgentKind.NATIVE:
@@ -923,7 +951,7 @@ class AgentsController:
923
951
  elif kind == AgentKind.ASSISTANT:
924
952
  client = self.get_assistant_client()
925
953
  else:
926
- raise ValueError("'kind' must be 'native'")
954
+ raise BadRequest("'kind' must be 'native'")
927
955
 
928
956
  draft_agents = client.get_draft_by_name(name)
929
957
  if len(draft_agents) > 1:
@@ -24,6 +24,8 @@ def chat_start(
24
24
  url = "http://localhost:3000/chat-lite"
25
25
  webbrowser.open(url)
26
26
  logger.info(f"Opening chat interface at {url}")
27
+ # TODO: Remove when connections UI is added
28
+ logger.warning("When using local chat, requests that the user 'Connect Apps' must be resolved by running `orchestrate connections set-credentials`")
27
29
  else:
28
30
  logger.error("Unable to start orchestrate UI chat service. Please check error messages and logs")
29
31
 
@@ -346,7 +346,7 @@ def add_connection(app_id: str) -> None:
346
346
  status_code = response.status_code
347
347
  try:
348
348
  if status_code == 409:
349
- response_text = f"Failed to create connection. A connection with the App ID '{app_id}' already exists. Please select a diffrent App ID or delete the existing resource."
349
+ response_text = f"Failed to create connection. A connection with the App ID '{app_id}' already exists. Please select a different App ID or delete the existing resource."
350
350
  else:
351
351
  resp = json.loads(response_text)
352
352
  response_text = resp.get('detail')
@@ -370,12 +370,14 @@ def remove_connection(app_id: str) -> None:
370
370
 
371
371
  def list_connections(environment: ConnectionEnvironment | None, verbose: bool = False) -> None:
372
372
  client = get_connections_client()
373
-
374
373
  connections = client.list()
375
-
374
+ is_local = is_local_dev()
375
+
376
376
  if verbose:
377
377
  connections_list = []
378
378
  for conn in connections:
379
+ if is_local and conn.environment == ConnectionEnvironment.LIVE:
380
+ continue
379
381
  connections_list.append(json.loads(conn.model_dump_json()))
380
382
 
381
383
  rich.print_json(json.dumps(connections_list, indent=4))
@@ -414,7 +416,7 @@ def list_connections(environment: ConnectionEnvironment | None, verbose: bool =
414
416
  conn.preference,
415
417
  "✅" if conn.credentials_entered else "❌"
416
418
  )
417
- elif conn.environment == ConnectionEnvironment.LIVE:
419
+ elif conn.environment == ConnectionEnvironment.LIVE and not is_local:
418
420
  live_table.add_row(
419
421
  conn.app_id,
420
422
  connection_type,
@@ -23,7 +23,7 @@ from ibm_watsonx_orchestrate.cli.config import (
23
23
  )
24
24
  from ibm_watsonx_orchestrate.client.client import Client
25
25
  from ibm_watsonx_orchestrate.client.client_errors import ClientError
26
- from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient, ClientAPIException
26
+ from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient, ClientAPIException
27
27
  from ibm_watsonx_orchestrate.client.credentials import Credentials
28
28
  from threading import Lock
29
29
  from ibm_watsonx_orchestrate.client.utils import is_local_dev, check_token_validity, is_cpd_env
@@ -55,13 +55,13 @@ def _validate_token_functionality(token: str, url: str) -> None:
55
55
  '''
56
56
  is_cpd = is_cpd_env(url)
57
57
  if is_cpd is True:
58
- agent_client = AgentClient(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
58
+ knowledge_base_client = KnowledgeBaseClient(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
59
59
  else:
60
- agent_client = AgentClient(base_url=url, api_key=token, is_local=is_local_dev(url))
61
- agent_client.api_key = token
60
+ knowledge_base_client = KnowledgeBaseClient(base_url=url, api_key=token, is_local=is_local_dev(url))
61
+ knowledge_base_client.api_key = token
62
62
 
63
63
  try:
64
- agent_client.get()
64
+ knowledge_base_client.get()
65
65
  except ClientAPIException as e:
66
66
  if e.response.status_code >= 400:
67
67
  reason = e.response.reason
@@ -167,7 +167,7 @@ def activate(name: str, apikey: str=None, username: str=None, password: str=None
167
167
 
168
168
  def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, type: EnvironmentAuthType=None, insecure: bool=None, verify: str=None) -> None:
169
169
  if name == PROTECTED_ENV_NAME:
170
- logger.error(f"The name '{PROTECTED_ENV_NAME}' is a reserved environment name. Please select a diffrent name or use `orchestrate env activate {PROTECTED_ENV_NAME}` to swap to '{PROTECTED_ENV_NAME}'")
170
+ logger.error(f"The name '{PROTECTED_ENV_NAME}' is a reserved environment name. Please select a different name or use `orchestrate env activate {PROTECTED_ENV_NAME}` to swap to '{PROTECTED_ENV_NAME}'")
171
171
  return
172
172
 
173
173
  cfg = Config()
@@ -4,6 +4,8 @@ from enum import Enum
4
4
  class EnvironmentAuthType(str, Enum):
5
5
  IBM_CLOUD_IAM = 'ibm_iam'
6
6
  MCSP = 'mcsp'
7
+ MCSP_V1 = 'mcsp_v1'
8
+ MCSP_V2 = 'mcsp_v2'
7
9
  CPD = 'cpd'
8
10
 
9
11
  def __str__(self):