ibm-watsonx-orchestrate 1.2.0__py3-none-any.whl → 1.4.2__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 +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +6 -1
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +16 -3
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +4 -20
- ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +13 -15
- ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +32 -0
- ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +42 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +14 -13
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +4 -2
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +29 -0
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +273 -12
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +79 -39
- ibm_watsonx_orchestrate/cli/commands/models/env_file_model_provider_mapper.py +180 -0
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +194 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +117 -48
- ibm_watsonx_orchestrate/cli/commands/server/types.py +105 -0
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +55 -7
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +123 -42
- ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +22 -1
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +209 -25
- ibm_watsonx_orchestrate/cli/init_helper.py +43 -0
- ibm_watsonx_orchestrate/cli/main.py +3 -1
- ibm_watsonx_orchestrate/client/agents/agent_client.py +4 -1
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +5 -1
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +5 -1
- ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +2 -6
- ibm_watsonx_orchestrate/client/base_api_client.py +5 -2
- ibm_watsonx_orchestrate/client/connections/connections_client.py +15 -21
- ibm_watsonx_orchestrate/client/model_policies/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +47 -0
- ibm_watsonx_orchestrate/client/model_policies/types.py +36 -0
- ibm_watsonx_orchestrate/client/models/__init__.py +0 -0
- ibm_watsonx_orchestrate/client/models/models_client.py +46 -0
- ibm_watsonx_orchestrate/client/models/types.py +177 -0
- ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py +15 -6
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +40 -0
- ibm_watsonx_orchestrate/client/tools/tool_client.py +8 -0
- ibm_watsonx_orchestrate/docker/compose-lite.yml +68 -13
- ibm_watsonx_orchestrate/docker/default.env +22 -12
- ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -1
- ibm_watsonx_orchestrate/experimental/flow_builder/__init__.py +0 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/__init__.py +41 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/constants.py +17 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/data_map.py +91 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +143 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/events.py +72 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/flow.py +1288 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/node.py +97 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/resources/flow_status.openapi.yml +98 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/types.py +492 -0
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +113 -0
- ibm_watsonx_orchestrate/utils/utils.py +5 -2
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/METADATA +6 -2
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/RECORD +59 -36
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.2.0.dist-info → ibm_watsonx_orchestrate-1.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -4,13 +4,16 @@ import rich
|
|
4
4
|
import requests
|
5
5
|
import importlib
|
6
6
|
import inspect
|
7
|
+
import zipfile
|
7
8
|
import sys
|
9
|
+
import io
|
8
10
|
import logging
|
9
11
|
from pathlib import Path
|
10
12
|
from copy import deepcopy
|
11
13
|
|
12
14
|
from typing import Iterable, List
|
13
|
-
from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import import_python_tool
|
15
|
+
from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import import_python_tool, ToolsController
|
16
|
+
from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_controller import import_python_knowledge_base
|
14
17
|
|
15
18
|
from ibm_watsonx_orchestrate.agent_builder.agents import (
|
16
19
|
Agent,
|
@@ -27,12 +30,14 @@ from ibm_watsonx_orchestrate.client.connections import get_connections_client
|
|
27
30
|
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
|
28
31
|
|
29
32
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
33
|
+
from ibm_watsonx_orchestrate.utils.utils import check_file_in_zip
|
30
34
|
|
31
35
|
logger = logging.getLogger(__name__)
|
32
36
|
|
33
37
|
def import_python_agent(file: str) -> List[Agent | ExternalAgent | AssistantAgent]:
|
34
38
|
# Import tools
|
35
39
|
import_python_tool(file)
|
40
|
+
import_python_knowledge_base(file)
|
36
41
|
|
37
42
|
file_path = Path(file)
|
38
43
|
file_directory = file_path.parent
|
@@ -140,10 +145,29 @@ def get_conn_id_from_app_id(app_id: str) -> str:
|
|
140
145
|
connections_client = get_connections_client()
|
141
146
|
connection = connections_client.get_draft_by_app_id(app_id=app_id)
|
142
147
|
if not connection:
|
143
|
-
logger.error(f"No connection
|
148
|
+
logger.error(f"No connection exists with the app-id '{app_id}'")
|
144
149
|
exit(1)
|
145
150
|
return connection.connection_id
|
146
151
|
|
152
|
+
def get_app_id_from_conn_id(conn_id: str) -> str:
|
153
|
+
connections_client = get_connections_client()
|
154
|
+
app_id = connections_client.get_draft_by_id(conn_id=conn_id)
|
155
|
+
if not app_id or app_id == conn_id:
|
156
|
+
logger.error(f"No connection exists with the connection id '{conn_id}'")
|
157
|
+
exit(1)
|
158
|
+
return app_id
|
159
|
+
|
160
|
+
def get_agent_details(name: str, client: AgentClient | ExternalAgentClient | AssistantAgentClient) -> dict:
|
161
|
+
agent_specs = client.get_draft_by_name(name)
|
162
|
+
if len(agent_specs) > 1:
|
163
|
+
logger.error(f"Multiple agents with the name '{name}' found. Failed to get agent")
|
164
|
+
sys.exit(1)
|
165
|
+
if len(agent_specs) == 0:
|
166
|
+
logger.error(f"No agents with the name '{name}' found. Failed to get agent")
|
167
|
+
sys.exit(1)
|
168
|
+
|
169
|
+
return agent_specs[0]
|
170
|
+
|
147
171
|
class AgentsController:
|
148
172
|
def __init__(self):
|
149
173
|
self.native_client = None
|
@@ -225,6 +249,7 @@ class AgentsController:
|
|
225
249
|
matching_external_agents = external_client.get_drafts_by_names(deref_agent.collaborators)
|
226
250
|
matching_assistant_agents = assistant_client.get_drafts_by_names(deref_agent.collaborators)
|
227
251
|
matching_agents = matching_native_agents + matching_external_agents + matching_assistant_agents
|
252
|
+
|
228
253
|
name_id_lookup = {}
|
229
254
|
for a in matching_agents:
|
230
255
|
if a.get("name") in name_id_lookup:
|
@@ -243,6 +268,35 @@ class AgentsController:
|
|
243
268
|
|
244
269
|
return deref_agent
|
245
270
|
|
271
|
+
def reference_collaborators(self, agent: Agent) -> Agent:
|
272
|
+
native_client = self.get_native_client()
|
273
|
+
external_client = self.get_external_client()
|
274
|
+
assistant_client = self.get_assistant_client()
|
275
|
+
|
276
|
+
ref_agent = deepcopy(agent)
|
277
|
+
matching_native_agents = native_client.get_drafts_by_ids(ref_agent.collaborators)
|
278
|
+
matching_external_agents = external_client.get_drafts_by_ids(ref_agent.collaborators)
|
279
|
+
matching_assistant_agents = assistant_client.get_drafts_by_ids(ref_agent.collaborators)
|
280
|
+
matching_agents = matching_native_agents + matching_external_agents + matching_assistant_agents
|
281
|
+
|
282
|
+
id_name_lookup = {}
|
283
|
+
for a in matching_agents:
|
284
|
+
if a.get("id") in id_name_lookup:
|
285
|
+
logger.error(f"Duplicate draft entries for collaborator '{a.get('id')}'")
|
286
|
+
sys.exit(1)
|
287
|
+
id_name_lookup[a.get("id")] = a.get("name")
|
288
|
+
|
289
|
+
ref_collaborators = []
|
290
|
+
for id in agent.collaborators:
|
291
|
+
name = id_name_lookup.get(id)
|
292
|
+
if not name:
|
293
|
+
logger.error(f"Failed to find collaborator. No agents found with the id '{id}'")
|
294
|
+
sys.exit(1)
|
295
|
+
ref_collaborators.append(name)
|
296
|
+
ref_agent.collaborators = ref_collaborators
|
297
|
+
|
298
|
+
return ref_agent
|
299
|
+
|
246
300
|
def dereference_tools(self, agent: Agent) -> Agent:
|
247
301
|
tool_client = self.get_tool_client()
|
248
302
|
|
@@ -252,7 +306,7 @@ class AgentsController:
|
|
252
306
|
name_id_lookup = {}
|
253
307
|
for tool in matching_tools:
|
254
308
|
if tool.get("name") in name_id_lookup:
|
255
|
-
logger.error(f"Duplicate draft entries for
|
309
|
+
logger.error(f"Duplicate draft entries for tool '{tool.get('name')}'")
|
256
310
|
sys.exit(1)
|
257
311
|
name_id_lookup[tool.get("name")] = tool.get("id")
|
258
312
|
|
@@ -267,6 +321,30 @@ class AgentsController:
|
|
267
321
|
|
268
322
|
return deref_agent
|
269
323
|
|
324
|
+
def reference_tools(self, agent: Agent) -> Agent:
|
325
|
+
tool_client = self.get_tool_client()
|
326
|
+
|
327
|
+
ref_agent = deepcopy(agent)
|
328
|
+
matching_tools = tool_client.get_drafts_by_ids(ref_agent.tools)
|
329
|
+
|
330
|
+
id_name_lookup = {}
|
331
|
+
for tool in matching_tools:
|
332
|
+
if tool.get("id") in id_name_lookup:
|
333
|
+
logger.error(f"Duplicate draft entries for tool '{tool.get('id')}'")
|
334
|
+
sys.exit(1)
|
335
|
+
id_name_lookup[tool.get("id")] = tool.get("name")
|
336
|
+
|
337
|
+
ref_tools = []
|
338
|
+
for id in agent.tools:
|
339
|
+
name = id_name_lookup[id]
|
340
|
+
if not name:
|
341
|
+
logger.error(f"Failed to find tool. No tools found with the id '{id}'")
|
342
|
+
sys.exit(1)
|
343
|
+
ref_tools.append(name)
|
344
|
+
ref_agent.tools = ref_tools
|
345
|
+
|
346
|
+
return ref_agent
|
347
|
+
|
270
348
|
def dereference_knowledge_bases(self, agent: Agent) -> Agent:
|
271
349
|
client = self.get_knowledge_base_client()
|
272
350
|
|
@@ -291,6 +369,22 @@ class AgentsController:
|
|
291
369
|
|
292
370
|
return deref_agent
|
293
371
|
|
372
|
+
def reference_knowledge_bases(self, agent: Agent) -> Agent:
|
373
|
+
client = self.get_knowledge_base_client()
|
374
|
+
|
375
|
+
ref_agent = deepcopy(agent)
|
376
|
+
|
377
|
+
ref_knowledge_bases = []
|
378
|
+
for id in agent.knowledge_base:
|
379
|
+
matching_knowledge_base = client.get_by_id(id)
|
380
|
+
name = matching_knowledge_base.get("name")
|
381
|
+
if not name:
|
382
|
+
logger.error(f"Failed to find knowledge base. No knowledge base found with the id '{id}'")
|
383
|
+
sys.exit(1)
|
384
|
+
ref_knowledge_bases.append(name)
|
385
|
+
ref_agent.knowledge_base = ref_knowledge_bases
|
386
|
+
return ref_agent
|
387
|
+
|
294
388
|
@staticmethod
|
295
389
|
def dereference_app_id(agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
296
390
|
if agent.kind == AgentKind.EXTERNAL:
|
@@ -299,6 +393,17 @@ class AgentsController:
|
|
299
393
|
agent.config.connection_id = get_conn_id_from_app_id(agent.config.app_id)
|
300
394
|
|
301
395
|
return agent
|
396
|
+
|
397
|
+
@staticmethod
|
398
|
+
def reference_app_id(agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
399
|
+
if agent.kind == AgentKind.EXTERNAL:
|
400
|
+
agent.app_id = get_app_id_from_conn_id(agent.connection_id)
|
401
|
+
agent.connection_id = None
|
402
|
+
else:
|
403
|
+
agent.config.app_id = get_app_id_from_conn_id(agent.config.connection_id)
|
404
|
+
agent.config.connection_id = None
|
405
|
+
|
406
|
+
return agent
|
302
407
|
|
303
408
|
|
304
409
|
def dereference_native_agent_dependencies(self, agent: Agent) -> Agent:
|
@@ -311,6 +416,16 @@ class AgentsController:
|
|
311
416
|
|
312
417
|
return agent
|
313
418
|
|
419
|
+
def reference_native_agent_dependencies(self, agent: Agent) -> Agent:
|
420
|
+
if agent.collaborators and len(agent.collaborators):
|
421
|
+
agent = self.reference_collaborators(agent)
|
422
|
+
if agent.tools and len(agent.tools):
|
423
|
+
agent = self.reference_tools(agent)
|
424
|
+
if agent.knowledge_base and len(agent.knowledge_base):
|
425
|
+
agent = self.reference_knowledge_bases(agent)
|
426
|
+
|
427
|
+
return agent
|
428
|
+
|
314
429
|
def dereference_external_or_assistant_agent_dependencies(self, agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
315
430
|
agent_dict = agent.model_dump()
|
316
431
|
|
@@ -318,13 +433,28 @@ class AgentsController:
|
|
318
433
|
agent = self.dereference_app_id(agent)
|
319
434
|
|
320
435
|
return agent
|
321
|
-
|
322
|
-
def
|
436
|
+
|
437
|
+
def reference_external_or_assistant_agent_dependencies(self, agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
438
|
+
agent_dict = agent.model_dump()
|
439
|
+
|
440
|
+
if agent_dict.get("connection_id") or agent.config.model_dump().get("connection_id"):
|
441
|
+
agent = self.reference_app_id(agent)
|
442
|
+
|
443
|
+
return agent
|
444
|
+
|
445
|
+
# Convert all names used in an agent to the corresponding ids
|
446
|
+
def dereference_agent_dependencies(self, agent: Agent | ExternalAgent | AssistantAgent ) -> Agent | ExternalAgent | AssistantAgent:
|
323
447
|
if isinstance(agent, Agent):
|
324
448
|
return self.dereference_native_agent_dependencies(agent)
|
325
449
|
if isinstance(agent, ExternalAgent) or isinstance(agent, AssistantAgent):
|
326
450
|
return self.dereference_external_or_assistant_agent_dependencies(agent)
|
327
|
-
|
451
|
+
|
452
|
+
# Convert all ids used in an agent to the corresponding names
|
453
|
+
def reference_agent_dependencies(self, agent: Agent | ExternalAgent | AssistantAgent ) -> Agent | ExternalAgent | AssistantAgent:
|
454
|
+
if isinstance(agent, Agent):
|
455
|
+
return self.reference_native_agent_dependencies(agent)
|
456
|
+
if isinstance(agent, ExternalAgent) or isinstance(agent, AssistantAgent):
|
457
|
+
return self.reference_external_or_assistant_agent_dependencies(agent)
|
328
458
|
|
329
459
|
def publish_or_update_agents(
|
330
460
|
self, agents: Iterable[Agent]
|
@@ -366,13 +496,13 @@ class AgentsController:
|
|
366
496
|
|
367
497
|
def publish_agent(self, agent: Agent, **kwargs) -> None:
|
368
498
|
if isinstance(agent, Agent):
|
369
|
-
self.get_native_client().create(agent.model_dump())
|
499
|
+
self.get_native_client().create(agent.model_dump(exclude_none=True))
|
370
500
|
logger.info(f"Agent '{agent.name}' imported successfully")
|
371
501
|
if isinstance(agent, ExternalAgent):
|
372
|
-
self.get_external_client().create(agent.model_dump())
|
502
|
+
self.get_external_client().create(agent.model_dump(exclude_none=True))
|
373
503
|
logger.info(f"External Agent '{agent.name}' imported successfully")
|
374
504
|
if isinstance(agent, AssistantAgent):
|
375
|
-
self.get_assistant_client().create(agent.model_dump(by_alias=True))
|
505
|
+
self.get_assistant_client().create(agent.model_dump(exclude_none=True, by_alias=True))
|
376
506
|
logger.info(f"Assistant Agent '{agent.name}' imported successfully")
|
377
507
|
|
378
508
|
def update_agent(
|
@@ -380,15 +510,15 @@ class AgentsController:
|
|
380
510
|
) -> None:
|
381
511
|
if isinstance(agent, Agent):
|
382
512
|
logger.info(f"Existing Agent '{agent.name}' found. Updating...")
|
383
|
-
self.get_native_client().update(agent_id, agent.model_dump())
|
513
|
+
self.get_native_client().update(agent_id, agent.model_dump(exclude_none=True))
|
384
514
|
logger.info(f"Agent '{agent.name}' updated successfully")
|
385
515
|
if isinstance(agent, ExternalAgent):
|
386
516
|
logger.info(f"Existing External Agent '{agent.name}' found. Updating...")
|
387
|
-
self.get_external_client().update(agent_id, agent.model_dump())
|
517
|
+
self.get_external_client().update(agent_id, agent.model_dump(exclude_none=True))
|
388
518
|
logger.info(f"External Agent '{agent.name}' updated successfully")
|
389
519
|
if isinstance(agent, AssistantAgent):
|
390
520
|
logger.info(f"Existing Assistant Agent '{agent.name}' found. Updating...")
|
391
|
-
self.get_assistant_client().update(agent_id, agent.model_dump(by_alias=True))
|
521
|
+
self.get_assistant_client().update(agent_id, agent.model_dump(exclude_none=True, by_alias=True))
|
392
522
|
logger.info(f"Assistant Agent '{agent.name}' updated successfully")
|
393
523
|
|
394
524
|
@staticmethod
|
@@ -657,4 +787,135 @@ class AgentsController:
|
|
657
787
|
except requests.HTTPError as e:
|
658
788
|
logger.error(e.response.text)
|
659
789
|
exit(1)
|
790
|
+
|
791
|
+
def get_spec_file_content(self, agent: Agent | ExternalAgent | AssistantAgent):
|
792
|
+
ref_agent = self.reference_agent_dependencies(agent)
|
793
|
+
agent_spec = ref_agent.model_dump(mode='json', exclude_none=True)
|
794
|
+
return agent_spec
|
795
|
+
|
796
|
+
def get_agent(self, name: str, kind: AgentKind) -> Agent | ExternalAgent | AssistantAgent:
|
797
|
+
match kind:
|
798
|
+
case AgentKind.NATIVE:
|
799
|
+
client = self.get_native_client()
|
800
|
+
agent_details = get_agent_details(name=name, client=client)
|
801
|
+
agent = Agent.model_validate(agent_details)
|
802
|
+
case AgentKind.EXTERNAL:
|
803
|
+
client = self.get_external_client()
|
804
|
+
agent_details = get_agent_details(name=name, client=client)
|
805
|
+
agent = ExternalAgent.model_validate(agent_details)
|
806
|
+
case AgentKind.ASSISTANT:
|
807
|
+
client = self.get_assistant_client()
|
808
|
+
agent_details = get_agent_details(name=name, client=client)
|
809
|
+
agent = AssistantAgent.model_validate(agent_details)
|
810
|
+
|
811
|
+
return agent
|
812
|
+
|
813
|
+
def get_agent_by_id(self, id: str) -> Agent | ExternalAgent | AssistantAgent:
|
814
|
+
native_client = self.get_native_client()
|
815
|
+
external_client = self.get_external_client()
|
816
|
+
assistant_client = self.get_assistant_client()
|
817
|
+
|
818
|
+
native_result = native_client.get_draft_by_id(id)
|
819
|
+
external_result = external_client.get_draft_by_id(id)
|
820
|
+
assistant_result = assistant_client.get_draft_by_id(id)
|
821
|
+
|
822
|
+
if native_result:
|
823
|
+
return Agent.model_validate(native_result)
|
824
|
+
if external_result:
|
825
|
+
return ExternalAgent.model_validate(external_result)
|
826
|
+
if assistant_result:
|
827
|
+
return AssistantAgent.model_validate(assistant_result)
|
828
|
+
|
829
|
+
|
830
|
+
def export_agent(self, name: str, kind: AgentKind, output_path: str, agent_only_flag: bool=False, zip_file_out: zipfile.ZipFile | None = None) -> None:
|
831
|
+
|
832
|
+
output_file = Path(output_path)
|
833
|
+
output_file_extension = output_file.suffix
|
834
|
+
output_file_name = output_file.stem
|
835
|
+
if not agent_only_flag and output_file_extension != ".zip":
|
836
|
+
logger.error(f"Output file must end with the extension '.zip'. Provided file '{output_path}' ends with '{output_file_extension}'")
|
837
|
+
sys.exit(1)
|
838
|
+
elif agent_only_flag and (output_file_extension != ".yaml" and output_file_extension != ".yml"):
|
839
|
+
logger.error(f"Output file must end with the extension '.yaml' or '.yml'. Provided file '{output_path}' ends with '{output_file_extension}'")
|
840
|
+
sys.exit(1)
|
841
|
+
|
842
|
+
agent = self.get_agent(name, kind)
|
843
|
+
agent_spec_file_content = self.get_spec_file_content(agent)
|
844
|
+
|
845
|
+
agent_spec_file_content.pop("hidden", None)
|
846
|
+
agent_spec_file_content.pop("id", None)
|
847
|
+
agent_spec_file_content["spec_version"] = SpecVersion.V1.value
|
848
|
+
|
849
|
+
if agent_only_flag:
|
850
|
+
logger.info(f"Exported agent definition for '{name}' to '{output_path}'")
|
851
|
+
with open(output_path, 'w') as outfile:
|
852
|
+
yaml.dump(agent_spec_file_content, outfile, sort_keys=False, default_flow_style=False)
|
853
|
+
return
|
854
|
+
|
855
|
+
close_file_flag = False
|
856
|
+
if zip_file_out is None:
|
857
|
+
close_file_flag = True
|
858
|
+
zip_file_out = zipfile.ZipFile(output_path, "w")
|
859
|
+
|
860
|
+
logger.info(f"Exporting agent definition for '{name}'")
|
861
|
+
|
862
|
+
agent_spec_yaml = yaml.dump(agent_spec_file_content, sort_keys=False, default_flow_style=False)
|
863
|
+
agent_spec_yaml_bytes = agent_spec_yaml.encode("utf-8")
|
864
|
+
agent_spec_yaml_file = io.BytesIO(agent_spec_yaml_bytes)
|
865
|
+
|
866
|
+
# Skip processing an agent if its already been saved
|
867
|
+
agent_file_path = f"{output_file_name}/agents/{agent_spec_file_content.get('kind', 'unknown')}/{agent_spec_file_content.get('name')}.yaml"
|
868
|
+
if check_file_in_zip(file_path=agent_file_path, zip_file=zip_file_out):
|
869
|
+
logger.warning(f"Skipping {agent_spec_file_content.get('name')}, agent with that name already exists in the output folder")
|
870
|
+
if close_file_flag:
|
871
|
+
zip_file_out.close()
|
872
|
+
return
|
873
|
+
|
874
|
+
zip_file_out.writestr(
|
875
|
+
agent_file_path,
|
876
|
+
agent_spec_yaml_file.getvalue()
|
877
|
+
)
|
878
|
+
|
879
|
+
tools_contoller = ToolsController()
|
880
|
+
for tool_name in agent_spec_file_content.get("tools", []):
|
881
|
+
|
882
|
+
base_tool_file_path = f"{output_file_name}/tools/{tool_name}/"
|
883
|
+
if check_file_in_zip(file_path=base_tool_file_path, zip_file=zip_file_out):
|
884
|
+
continue
|
885
|
+
|
886
|
+
logger.info(f"Exporting tool '{tool_name}'")
|
887
|
+
tool_artifact_bytes = tools_contoller.download_tool(tool_name)
|
888
|
+
if not tool_artifact_bytes:
|
889
|
+
continue
|
890
|
+
|
891
|
+
with zipfile.ZipFile(io.BytesIO(tool_artifact_bytes), "r") as zip_file_in:
|
892
|
+
for item in zip_file_in.infolist():
|
893
|
+
buffer = zip_file_in.read(item.filename)
|
894
|
+
if (item.filename != 'bundle-format'):
|
895
|
+
zip_file_out.writestr(
|
896
|
+
f"{base_tool_file_path}{item.filename}",
|
897
|
+
buffer
|
898
|
+
)
|
899
|
+
|
900
|
+
for kb_name in agent_spec_file_content.get("knowledge_base", []):
|
901
|
+
logger.warning(f"Skipping {kb_name}, knowledge_bases are currently unsupported by export")
|
902
|
+
|
903
|
+
if kind == AgentKind.NATIVE:
|
904
|
+
for collaborator_id in agent.collaborators:
|
905
|
+
collaborator = self.get_agent_by_id(collaborator_id)
|
906
|
+
|
907
|
+
if not collaborator:
|
908
|
+
logger.warning(f"Skipping {collaborator_id}, no agent with id {collaborator_id} found")
|
909
|
+
continue
|
910
|
+
|
911
|
+
self.export_agent(
|
912
|
+
name=collaborator.name,
|
913
|
+
kind=collaborator.kind,
|
914
|
+
output_path=output_path,
|
915
|
+
agent_only_flag=False,
|
916
|
+
zip_file_out=zip_file_out)
|
917
|
+
|
918
|
+
if close_file_flag:
|
919
|
+
logger.info(f"Successfully wrote agents and tools to '{output_path}'")
|
920
|
+
zip_file_out.close()
|
660
921
|
|
@@ -9,7 +9,7 @@ knowledge_bases_app = typer.Typer(no_args_is_help=True)
|
|
9
9
|
def knowledge_base_import(
|
10
10
|
file: Annotated[
|
11
11
|
str,
|
12
|
-
typer.Option("--file", "-f", help="YAML file with knowledge base definition"),
|
12
|
+
typer.Option("--file", "-f", help="YAML, JSON or Python file with knowledge base definition(s)"),
|
13
13
|
],
|
14
14
|
app_id: Annotated[
|
15
15
|
str, typer.Option(
|
@@ -25,7 +25,7 @@ def knowledge_base_import(
|
|
25
25
|
def knowledge_base_patch(
|
26
26
|
file: Annotated[
|
27
27
|
str,
|
28
|
-
typer.Option("--file", "-f", help="YAML file with knowledge base definition"),
|
28
|
+
typer.Option("--file", "-f", help="YAML or JSON file with knowledge base definition"),
|
29
29
|
],
|
30
30
|
name: Annotated[
|
31
31
|
str,
|
@@ -3,17 +3,44 @@ import json
|
|
3
3
|
import rich
|
4
4
|
import requests
|
5
5
|
import logging
|
6
|
+
import importlib
|
7
|
+
import inspect
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import List
|
6
10
|
|
7
|
-
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base_requests import
|
11
|
+
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base_requests import KnowledgeBaseUpdateRequest
|
8
12
|
from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import KnowledgeBase
|
9
13
|
from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
|
10
14
|
from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
|
11
15
|
from ibm_watsonx_orchestrate.client.connections import get_connections_client
|
12
|
-
|
13
16
|
from ibm_watsonx_orchestrate.client.utils import instantiate_client
|
14
17
|
|
15
18
|
logger = logging.getLogger(__name__)
|
16
19
|
|
20
|
+
def import_python_knowledge_base(file: str) -> List[KnowledgeBase]:
|
21
|
+
file_path = Path(file)
|
22
|
+
file_directory = file_path.parent
|
23
|
+
file_name = file_path.stem
|
24
|
+
sys.path.append(str(file_directory))
|
25
|
+
module = importlib.import_module(file_name)
|
26
|
+
del sys.path[-1]
|
27
|
+
|
28
|
+
knowledge_bases = []
|
29
|
+
for _, obj in inspect.getmembers(module):
|
30
|
+
if isinstance(obj, KnowledgeBase):
|
31
|
+
knowledge_bases.append(obj)
|
32
|
+
return knowledge_bases
|
33
|
+
|
34
|
+
def parse_file(file: str) -> List[KnowledgeBase]:
|
35
|
+
if file.endswith('.yaml') or file.endswith('.yml') or file.endswith(".json"):
|
36
|
+
knowledge_base = KnowledgeBase.from_spec(file=file)
|
37
|
+
return [knowledge_base]
|
38
|
+
elif file.endswith('.py'):
|
39
|
+
knowledge_bases = import_python_knowledge_base(file)
|
40
|
+
return knowledge_bases
|
41
|
+
else:
|
42
|
+
raise ValueError("file must end in .json, .yaml, .yml or .py")
|
43
|
+
|
17
44
|
def to_column_name(col: str):
|
18
45
|
return " ".join([word.capitalize() if not word[0].isupper() else word for word in col.split("_")])
|
19
46
|
|
@@ -22,6 +49,15 @@ def get_file_name(path: str):
|
|
22
49
|
# return to_column_name(path.split("/")[-1].split(".")[0])
|
23
50
|
return path.split("/")[-1]
|
24
51
|
|
52
|
+
def get_relative_file_path(path, dir):
|
53
|
+
if path.startswith("/"):
|
54
|
+
return path
|
55
|
+
elif path.startswith("./"):
|
56
|
+
return f"{dir}{path.removeprefix('.')}"
|
57
|
+
else:
|
58
|
+
return f"{dir}/{path}"
|
59
|
+
|
60
|
+
|
25
61
|
class KnowledgeBaseController:
|
26
62
|
def __init__(self):
|
27
63
|
self.client = None
|
@@ -34,45 +70,46 @@ class KnowledgeBaseController:
|
|
34
70
|
|
35
71
|
def import_knowledge_base(self, file: str, app_id: str):
|
36
72
|
client = self.get_client()
|
37
|
-
create_request = KnowledgeBaseCreateRequest.from_spec(file=file)
|
38
73
|
|
39
|
-
|
40
|
-
|
41
|
-
|
74
|
+
knowledge_bases = parse_file(file=file)
|
75
|
+
for kb in knowledge_bases:
|
76
|
+
try:
|
77
|
+
kb.validate_documents_or_index_exists()
|
78
|
+
if kb.documents:
|
42
79
|
file_dir = "/".join(file.split("/")[:-1])
|
43
|
-
files = [('files', (get_file_name(file_path), open(file_path
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
80
|
+
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in kb.documents]
|
81
|
+
|
82
|
+
kb.prioritize_built_in_index = True
|
83
|
+
payload = kb.model_dump(exclude_none=True);
|
84
|
+
payload.pop('documents');
|
85
|
+
|
86
|
+
client.create_built_in(payload=payload, files=files)
|
87
|
+
else:
|
88
|
+
if len(kb.conversational_search_tool.index_config) != 1:
|
89
|
+
raise ValueError(f"Must provide exactly one conversational_search_tool.index_config. Provided {len(kb.conversational_search_tool.index_config)}.")
|
90
|
+
|
91
|
+
|
92
|
+
if app_id:
|
93
|
+
connections_client = get_connections_client()
|
94
|
+
connection_id = None
|
95
|
+
if app_id is not None:
|
96
|
+
connections = connections_client.get_draft_by_app_id(app_id=app_id)
|
97
|
+
if not connections:
|
98
|
+
logger.error(f"No connection exists with the app-id '{app_id}'")
|
99
|
+
exit(1)
|
100
|
+
|
101
|
+
connection_id = connections.connection_id
|
102
|
+
kb.conversational_search_tool.index_config[0].connection_id = connection_id
|
103
|
+
|
104
|
+
kb.prioritize_built_in_index = False
|
105
|
+
client.create(payload=kb.model_dump(exclude_none=True))
|
55
106
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
logger.error(f"No connection exists with the app-id '{app_id}'")
|
63
|
-
exit(1)
|
64
|
-
|
65
|
-
connection_id = connections.connection_id
|
66
|
-
create_request.conversational_search_tool.index_config[0].connection_id = connection_id
|
67
|
-
|
68
|
-
client.create(payload=create_request.model_dump(exclude_none=True))
|
69
|
-
|
70
|
-
logger.info(f"Successfully imported knowledge base '{create_request.name}'")
|
71
|
-
except ClientAPIException as e:
|
72
|
-
if "duplicate key value violates unique constraint" in e.response.text:
|
73
|
-
logger.error(f"A knowledge base with the name '{create_request.name}' already exists. Failed to import knowledge base")
|
74
|
-
else:
|
75
|
-
logger.error(f"Error importing knowledge base '{create_request.name}\n' {e.response.text}")
|
107
|
+
logger.info(f"Successfully imported knowledge base '{kb.name}'")
|
108
|
+
except ClientAPIException as e:
|
109
|
+
if "duplicate key value violates unique constraint" in e.response.text:
|
110
|
+
logger.error(f"A knowledge base with the name '{kb.name}' already exists. Failed to import knowledge base")
|
111
|
+
else:
|
112
|
+
logger.error(f"Error importing knowledge base '{kb.name}\n' {e.response.text}")
|
76
113
|
|
77
114
|
def get_id(
|
78
115
|
self, id: str, name: str
|
@@ -101,13 +138,16 @@ class KnowledgeBaseController:
|
|
101
138
|
|
102
139
|
if update_request.documents:
|
103
140
|
file_dir = "/".join(file.split("/")[:-1])
|
104
|
-
files = [('files', (get_file_name(file_path), open(file_path
|
141
|
+
files = [('files', (get_file_name(file_path), open(get_relative_file_path(file_path, file_dir), 'rb'))) for file_path in update_request.documents]
|
105
142
|
|
143
|
+
update_request.prioritize_built_in_index = True
|
106
144
|
payload = update_request.model_dump(exclude_none=True);
|
107
145
|
payload.pop('documents');
|
108
146
|
|
109
147
|
self.get_client().update_with_documents(knowledge_base_id, payload=payload, files=files)
|
110
148
|
else:
|
149
|
+
if update_request.conversational_search_tool and update_request.conversational_search_tool.index_config:
|
150
|
+
update_request.prioritize_built_in_index = False
|
111
151
|
self.get_client().update(knowledge_base_id, update_request.model_dump(exclude_none=True))
|
112
152
|
|
113
153
|
logEnding = f"with ID '{id}'" if id else f"'{name}'"
|