ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.6.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 +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
- ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +34 -0
- ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
- ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
- ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
- ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
- ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
- ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
- ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
- ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
- ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +153 -21
- ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
- ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +2 -0
- ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
- ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +61 -61
- ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
- ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
- ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +312 -0
- ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +171 -0
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
- ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
- ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
- ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py +147 -21
- ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
- ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +111 -32
- ibm_watsonx_orchestrate/cli/config.py +2 -0
- ibm_watsonx_orchestrate/cli/main.py +6 -0
- ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
- ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
- ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
- ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
- ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
- ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
- ibm_watsonx_orchestrate/client/credentials.py +4 -0
- ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
- ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
- ibm_watsonx_orchestrate/client/service_instance.py +42 -1
- ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
- ibm_watsonx_orchestrate/client/utils.py +37 -2
- ibm_watsonx_orchestrate/docker/compose-lite.yml +252 -81
- ibm_watsonx_orchestrate/docker/default.env +40 -15
- ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
- ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
- ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
- ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
- ibm_watsonx_orchestrate/run/connections.py +4 -4
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/METADATA +2 -1
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/RECORD +69 -62
- ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
- ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
- /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/WHEEL +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/entry_points.txt +0 -0
- {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -25,7 +25,7 @@ from ibm_watsonx_orchestrate.agent_builder.agents import (
|
|
25
25
|
AgentKind,
|
26
26
|
SpecVersion
|
27
27
|
)
|
28
|
-
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
|
28
|
+
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient, AgentUpsertResponse
|
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
|
@@ -99,6 +99,7 @@ def parse_create_native_args(name: str, kind: AgentKind, description: str | None
|
|
99
99
|
"style": args.get("style"),
|
100
100
|
"custom_join_tool": args.get("custom_join_tool"),
|
101
101
|
"structured_output": args.get("structured_output"),
|
102
|
+
"context_access_enabled": args.get("context_access_enabled", True),
|
102
103
|
}
|
103
104
|
|
104
105
|
collaborators = args.get("collaborators", [])
|
@@ -116,6 +117,23 @@ def parse_create_native_args(name: str, kind: AgentKind, description: str | None
|
|
116
117
|
knowledge_base = [x.strip() for x in knowledge_base if x.strip() != ""]
|
117
118
|
agent_details["knowledge_base"] = knowledge_base
|
118
119
|
|
120
|
+
context_variables = args.get("context_variables", [])
|
121
|
+
context_variables = context_variables if context_variables else []
|
122
|
+
context_variables = [x.strip() for x in context_variables if x.strip() != ""]
|
123
|
+
agent_details["context_variables"] = context_variables
|
124
|
+
|
125
|
+
# hidden = args.get("hidden")
|
126
|
+
# if hidden:
|
127
|
+
# agent_details["hidden"] = hidden
|
128
|
+
|
129
|
+
# starter_prompts = args.get("starter_prompts")
|
130
|
+
# if starter_prompts:
|
131
|
+
# agent_details["starter_prompts"] = starter_prompts
|
132
|
+
|
133
|
+
# welcome_content = args.get("welcome_content")
|
134
|
+
# if welcome_content:
|
135
|
+
# agent_details["welcome_content"] = welcome_content
|
136
|
+
|
119
137
|
return agent_details
|
120
138
|
|
121
139
|
def parse_create_external_args(name: str, kind: AgentKind, description: str | None, **args) -> dict:
|
@@ -133,8 +151,14 @@ def parse_create_external_args(name: str, kind: AgentKind, description: str | No
|
|
133
151
|
"config": args.get("config", {}),
|
134
152
|
"nickname": args.get("nickname"),
|
135
153
|
"app_id": args.get("app_id"),
|
154
|
+
"context_access_enabled": args.get("context_access_enabled", True),
|
136
155
|
}
|
137
156
|
|
157
|
+
context_variables = args.get("context_variables", [])
|
158
|
+
context_variables = context_variables if context_variables else []
|
159
|
+
context_variables = [x.strip() for x in context_variables if x.strip() != ""]
|
160
|
+
agent_details["context_variables"] = context_variables
|
161
|
+
|
138
162
|
return agent_details
|
139
163
|
|
140
164
|
def parse_create_assistant_args(name: str, kind: AgentKind, description: str | None, **args) -> dict:
|
@@ -146,8 +170,14 @@ def parse_create_assistant_args(name: str, kind: AgentKind, description: str | N
|
|
146
170
|
"tags": args.get("tags", []),
|
147
171
|
"config": args.get("config", {}),
|
148
172
|
"nickname": args.get("nickname"),
|
173
|
+
"context_access_enabled": args.get("context_access_enabled", True),
|
149
174
|
}
|
150
175
|
|
176
|
+
context_variables = args.get("context_variables", [])
|
177
|
+
context_variables = context_variables if context_variables else []
|
178
|
+
context_variables = [x.strip() for x in context_variables if x.strip() != ""]
|
179
|
+
agent_details["context_variables"] = context_variables
|
180
|
+
|
151
181
|
return agent_details
|
152
182
|
|
153
183
|
def get_conn_id_from_app_id(app_id: str) -> str:
|
@@ -177,6 +207,10 @@ def get_agent_details(name: str, client: AgentClient | ExternalAgentClient | Ass
|
|
177
207
|
|
178
208
|
return agent_specs[0]
|
179
209
|
|
210
|
+
def _raise_guidelines_warning(response: AgentUpsertResponse) -> None:
|
211
|
+
if response.warning:
|
212
|
+
logger.warning(f"Agent Configuration Issue: {response.warning}")
|
213
|
+
|
180
214
|
class AgentsController:
|
181
215
|
def __init__(self):
|
182
216
|
self.native_client = None
|
@@ -420,6 +454,72 @@ class AgentsController:
|
|
420
454
|
ref_agent.knowledge_base = ref_knowledge_bases
|
421
455
|
return ref_agent
|
422
456
|
|
457
|
+
def dereference_guidelines(self, agent: Agent) -> Agent:
|
458
|
+
tool_client = self.get_tool_client()
|
459
|
+
|
460
|
+
guideline_tool_names = set()
|
461
|
+
|
462
|
+
for guideline in agent.guidelines:
|
463
|
+
if guideline.tool:
|
464
|
+
guideline_tool_names.add(guideline.tool)
|
465
|
+
|
466
|
+
if len(guideline_tool_names) == 0:
|
467
|
+
return agent
|
468
|
+
|
469
|
+
deref_agent = deepcopy(agent)
|
470
|
+
|
471
|
+
matching_tools = tool_client.get_drafts_by_names(list(guideline_tool_names))
|
472
|
+
|
473
|
+
name_id_lookup = {}
|
474
|
+
for tool in matching_tools:
|
475
|
+
if tool.get("name") in name_id_lookup:
|
476
|
+
logger.error(f"Duplicate draft entries for tool '{tool.get('name')}'")
|
477
|
+
sys.exit(1)
|
478
|
+
name_id_lookup[tool.get("name")] = tool.get("id")
|
479
|
+
|
480
|
+
for guideline in deref_agent.guidelines:
|
481
|
+
if guideline.tool:
|
482
|
+
id = name_id_lookup.get(guideline.tool)
|
483
|
+
if not id:
|
484
|
+
logger.error(f"Failed to find guideline tool. No tools found with the name '{guideline.tool}'")
|
485
|
+
sys.exit(1)
|
486
|
+
guideline.tool = id
|
487
|
+
|
488
|
+
return deref_agent
|
489
|
+
|
490
|
+
def reference_guidelines(self, agent: Agent) -> Agent:
|
491
|
+
tool_client = self.get_tool_client()
|
492
|
+
|
493
|
+
guideline_tool_ids = set()
|
494
|
+
|
495
|
+
for guideline in agent.guidelines:
|
496
|
+
if guideline.tool:
|
497
|
+
guideline_tool_ids.add(guideline.tool)
|
498
|
+
|
499
|
+
if len(guideline_tool_ids) == 0:
|
500
|
+
return agent
|
501
|
+
|
502
|
+
ref_agent = deepcopy(agent)
|
503
|
+
|
504
|
+
matching_tools = tool_client.get_drafts_by_ids(list(guideline_tool_ids))
|
505
|
+
|
506
|
+
id_name_lookup = {}
|
507
|
+
for tool in matching_tools:
|
508
|
+
if tool.get("id") in id_name_lookup:
|
509
|
+
logger.error(f"Duplicate draft entries for tool '{tool.get('id')}'")
|
510
|
+
sys.exit(1)
|
511
|
+
id_name_lookup[tool.get("id")] = tool.get("name")
|
512
|
+
|
513
|
+
for guideline in ref_agent.guidelines:
|
514
|
+
if guideline.tool:
|
515
|
+
name = id_name_lookup.get(guideline.tool)
|
516
|
+
if not name:
|
517
|
+
logger.error(f"Failed to find guideline tool. No tools found with the id '{guideline.tool}'")
|
518
|
+
sys.exit(1)
|
519
|
+
guideline.tool = name
|
520
|
+
|
521
|
+
return ref_agent
|
522
|
+
|
423
523
|
@staticmethod
|
424
524
|
def dereference_app_id(agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
425
525
|
if agent.kind == AgentKind.EXTERNAL:
|
@@ -448,6 +548,8 @@ class AgentsController:
|
|
448
548
|
agent = self.dereference_tools(agent)
|
449
549
|
if agent.knowledge_base and len(agent.knowledge_base):
|
450
550
|
agent = self.dereference_knowledge_bases(agent)
|
551
|
+
if agent.guidelines and len(agent.guidelines):
|
552
|
+
agent = self.dereference_guidelines(agent)
|
451
553
|
|
452
554
|
return agent
|
453
555
|
|
@@ -458,10 +560,12 @@ class AgentsController:
|
|
458
560
|
agent = self.reference_tools(agent)
|
459
561
|
if agent.knowledge_base and len(agent.knowledge_base):
|
460
562
|
agent = self.reference_knowledge_bases(agent)
|
563
|
+
if agent.guidelines and len(agent.guidelines):
|
564
|
+
agent = self.reference_guidelines(agent)
|
461
565
|
|
462
566
|
return agent
|
463
567
|
|
464
|
-
def dereference_external_or_assistant_agent_dependencies(self, agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
568
|
+
def dereference_external_or_assistant_agent_dependencies(self, agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
465
569
|
agent_dict = agent.model_dump()
|
466
570
|
|
467
571
|
if agent_dict.get("app_id") or agent.config.model_dump().get("app_id"):
|
@@ -469,7 +573,7 @@ class AgentsController:
|
|
469
573
|
|
470
574
|
return agent
|
471
575
|
|
472
|
-
def reference_external_or_assistant_agent_dependencies(self, agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
576
|
+
def reference_external_or_assistant_agent_dependencies(self, agent: ExternalAgent | AssistantAgent) -> ExternalAgent | AssistantAgent:
|
473
577
|
agent_dict = agent.model_dump()
|
474
578
|
|
475
579
|
if agent_dict.get("connection_id") or agent.config.model_dump().get("connection_id"):
|
@@ -543,7 +647,8 @@ class AgentsController:
|
|
543
647
|
|
544
648
|
def publish_agent(self, agent: Agent, **kwargs) -> None:
|
545
649
|
if isinstance(agent, Agent):
|
546
|
-
self.get_native_client().create(agent.model_dump(exclude_none=True))
|
650
|
+
response = self.get_native_client().create(agent.model_dump(exclude_none=True))
|
651
|
+
_raise_guidelines_warning(response)
|
547
652
|
logger.info(f"Agent '{agent.name}' imported successfully")
|
548
653
|
if isinstance(agent, ExternalAgent):
|
549
654
|
self.get_external_client().create(agent.model_dump(exclude_none=True))
|
@@ -557,7 +662,8 @@ class AgentsController:
|
|
557
662
|
) -> None:
|
558
663
|
if isinstance(agent, Agent):
|
559
664
|
logger.info(f"Existing Agent '{agent.name}' found. Updating...")
|
560
|
-
self.get_native_client().update(agent_id, agent.model_dump(exclude_none=True))
|
665
|
+
response = self.get_native_client().update(agent_id, agent.model_dump(exclude_none=True))
|
666
|
+
_raise_guidelines_warning(response)
|
561
667
|
logger.info(f"Agent '{agent.name}' updated successfully")
|
562
668
|
if isinstance(agent, ExternalAgent):
|
563
669
|
logger.info(f"Existing External Agent '{agent.name}' found. Updating...")
|
@@ -641,14 +747,25 @@ class AgentsController:
|
|
641
747
|
return knowledge_bases
|
642
748
|
|
643
749
|
def list_agents(self, kind: AgentKind=None, verbose: bool=False):
|
750
|
+
parse_errors = []
|
751
|
+
|
644
752
|
if kind == AgentKind.NATIVE or kind is None:
|
645
753
|
response = self.get_native_client().get()
|
646
|
-
native_agents = [
|
754
|
+
native_agents = []
|
755
|
+
for agent in response:
|
756
|
+
try:
|
757
|
+
native_agents.append(Agent.model_validate(agent))
|
758
|
+
except Exception as e:
|
759
|
+
name = agent.get('name', None)
|
760
|
+
parse_errors.append([
|
761
|
+
f"Agent '{name}' could not be parsed",
|
762
|
+
json.dumps(agent),
|
763
|
+
e
|
764
|
+
])
|
647
765
|
|
648
766
|
if verbose:
|
649
767
|
agents_list = []
|
650
768
|
for agent in native_agents:
|
651
|
-
|
652
769
|
agents_list.append(json.loads(agent.dumps_spec()))
|
653
770
|
|
654
771
|
rich.print(rich.json.JSON(json.dumps(agents_list, indent=4)))
|
@@ -660,14 +777,14 @@ class AgentsController:
|
|
660
777
|
show_lines=True
|
661
778
|
)
|
662
779
|
column_args = {
|
663
|
-
"Name": {},
|
780
|
+
"Name": {"overflow": "fold"},
|
664
781
|
"Description": {},
|
665
782
|
"LLM": {"overflow": "fold"},
|
666
783
|
"Style": {},
|
667
784
|
"Collaborators": {},
|
668
785
|
"Tools": {},
|
669
786
|
"Knowledge Base": {},
|
670
|
-
"ID": {},
|
787
|
+
"ID": {"overflow": "fold"},
|
671
788
|
}
|
672
789
|
for column in column_args:
|
673
790
|
native_table.add_column(column, **column_args[column])
|
@@ -693,7 +810,13 @@ class AgentsController:
|
|
693
810
|
if kind == AgentKind.EXTERNAL or kind is None:
|
694
811
|
response = self.get_external_client().get()
|
695
812
|
|
696
|
-
external_agents = [
|
813
|
+
external_agents = []
|
814
|
+
for agent in response:
|
815
|
+
try:
|
816
|
+
external_agents.append(ExternalAgent.model_validate(agent))
|
817
|
+
except Exception as e:
|
818
|
+
name = agent.get('name', None)
|
819
|
+
parse_errors.append([f"External Agent {name} could not be parsed", e])
|
697
820
|
|
698
821
|
response_dict = {agent["id"]: agent for agent in response}
|
699
822
|
|
@@ -717,7 +840,7 @@ class AgentsController:
|
|
717
840
|
show_lines=True
|
718
841
|
)
|
719
842
|
column_args = {
|
720
|
-
"Name": {},
|
843
|
+
"Name": {"overflow": "fold"},
|
721
844
|
"Title": {},
|
722
845
|
"Description": {},
|
723
846
|
"Tags": {},
|
@@ -725,9 +848,9 @@ class AgentsController:
|
|
725
848
|
"Chat Params": {},
|
726
849
|
"Config": {},
|
727
850
|
"Nickname": {},
|
728
|
-
"App ID": {},
|
729
|
-
"ID": {}
|
730
|
-
|
851
|
+
"App ID": {"overflow": "fold"},
|
852
|
+
"ID": {"overflow": "fold"}
|
853
|
+
}
|
731
854
|
|
732
855
|
for column in column_args:
|
733
856
|
external_table.add_column(column, **column_args[column])
|
@@ -753,7 +876,13 @@ class AgentsController:
|
|
753
876
|
if kind == AgentKind.ASSISTANT or kind is None:
|
754
877
|
response = self.get_assistant_client().get()
|
755
878
|
|
756
|
-
assistant_agents = [
|
879
|
+
assistant_agents = []
|
880
|
+
for agent in response:
|
881
|
+
try:
|
882
|
+
assistant_agents.append(AssistantAgent.model_validate(agent))
|
883
|
+
except Exception as e:
|
884
|
+
name = agent.get('name', None)
|
885
|
+
parse_errors.append([f"Assistant Agent {name} could not be parsed", e])
|
757
886
|
|
758
887
|
response_dict = {agent["id"]: agent for agent in response}
|
759
888
|
|
@@ -778,17 +907,17 @@ class AgentsController:
|
|
778
907
|
title="Assistant Agents",
|
779
908
|
show_lines=True)
|
780
909
|
column_args = {
|
781
|
-
"Name": {},
|
910
|
+
"Name": {"overflow": "fold"},
|
782
911
|
"Title": {},
|
783
912
|
"Description": {},
|
784
913
|
"Tags": {},
|
785
914
|
"Nickname": {},
|
786
915
|
"CRN": {},
|
787
916
|
"Instance URL": {},
|
788
|
-
"Assistant ID": {},
|
789
|
-
"Environment ID": {},
|
790
|
-
"ID": {}
|
791
|
-
|
917
|
+
"Assistant ID": {"overflow": "fold"},
|
918
|
+
"Environment ID": {"overflow": "fold"},
|
919
|
+
"ID": {"overflow": "fold"}
|
920
|
+
}
|
792
921
|
|
793
922
|
for column in column_args:
|
794
923
|
assistants_table.add_column(column, **column_args[column])
|
@@ -808,6 +937,10 @@ class AgentsController:
|
|
808
937
|
)
|
809
938
|
rich.print(assistants_table)
|
810
939
|
|
940
|
+
for error in parse_errors:
|
941
|
+
for l in error:
|
942
|
+
logger.error(l)
|
943
|
+
|
811
944
|
def remove_agent(self, name: str, kind: AgentKind):
|
812
945
|
try:
|
813
946
|
if kind == AgentKind.NATIVE:
|
@@ -875,7 +1008,6 @@ class AgentsController:
|
|
875
1008
|
|
876
1009
|
|
877
1010
|
def export_agent(self, name: str, kind: AgentKind, output_path: str, agent_only_flag: bool=False, zip_file_out: zipfile.ZipFile | None = None) -> None:
|
878
|
-
|
879
1011
|
output_file = Path(output_path)
|
880
1012
|
output_file_extension = output_file.suffix
|
881
1013
|
output_file_name = output_file.stem
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import logging
|
2
|
-
import logging
|
3
2
|
import rich
|
4
3
|
import jwt
|
4
|
+
import sys
|
5
5
|
|
6
6
|
from ibm_watsonx_orchestrate.cli.config import Config, ENV_WXO_URL_OPT, ENVIRONMENTS_SECTION_HEADER, CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, CHAT_UI_PORT
|
7
|
-
from ibm_watsonx_orchestrate.client.utils import is_local_dev, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT, AUTH_CONFIG_FILE
|
7
|
+
from ibm_watsonx_orchestrate.client.utils import is_local_dev, is_ibm_cloud, AUTH_CONFIG_FILE_FOLDER, AUTH_SECTION_HEADER, AUTH_MCSP_TOKEN_OPT, AUTH_CONFIG_FILE
|
8
8
|
|
9
9
|
from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
|
10
10
|
|
@@ -20,7 +20,41 @@ class ChannelsWebchatController:
|
|
20
20
|
def get_native_client(self):
|
21
21
|
self.native_client = instantiate_client(AgentClient)
|
22
22
|
return self.native_client
|
23
|
-
|
23
|
+
|
24
|
+
def extract_tenant_id_from_crn(self, crn: str) -> str:
|
25
|
+
is_ibm_cloud_env = is_ibm_cloud()
|
26
|
+
if is_ibm_cloud_env:
|
27
|
+
try:
|
28
|
+
parts = crn.split("a/")[1].split(":")
|
29
|
+
account_part = parts[0]
|
30
|
+
instance_part = parts[1]
|
31
|
+
tenant_id = f"{account_part}_{instance_part}"
|
32
|
+
return tenant_id
|
33
|
+
except (IndexError, AttributeError):
|
34
|
+
raise ValueError(f"Invalid CRN format: {crn}")
|
35
|
+
else:
|
36
|
+
try:
|
37
|
+
parts = crn.split("sub/")[1].split(":")
|
38
|
+
account_part = parts[0]
|
39
|
+
instance_part = parts[1]
|
40
|
+
tenant_id = f"{account_part}_{instance_part}"
|
41
|
+
return tenant_id
|
42
|
+
except (IndexError, AttributeError):
|
43
|
+
raise ValueError(f"Invalid CRN format: {crn}")
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
def check_crn_is_correct(self, crn: str):
|
48
|
+
parts = crn.split("a/")[1].split(":")
|
49
|
+
instance_part_crn = parts[1]
|
50
|
+
cfg = Config()
|
51
|
+
active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
52
|
+
url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
|
53
|
+
instance_part_url = url.rstrip("/").split("/")[-1]
|
54
|
+
if instance_part_crn == instance_part_url:
|
55
|
+
return True
|
56
|
+
else:
|
57
|
+
return False
|
24
58
|
|
25
59
|
def get_agent_id(self, agent_name: str):
|
26
60
|
native_client = self.get_native_client()
|
@@ -40,7 +74,7 @@ class ChannelsWebchatController:
|
|
40
74
|
raise ValueError(f"No agent found with the name '{agent_name}'")
|
41
75
|
|
42
76
|
agent = existing_native_agents[0]
|
43
|
-
agent_environments = agent.get("environments", [])
|
77
|
+
agent_environments = agent.get("environments", [])
|
44
78
|
|
45
79
|
is_local = is_local_dev()
|
46
80
|
target_env = env or 'draft'
|
@@ -57,15 +91,37 @@ class ChannelsWebchatController:
|
|
57
91
|
logger.error(f'This agent does not exist in the {env} environment. You need to deploy it to {env} before you can embed the agent')
|
58
92
|
exit(1)
|
59
93
|
|
60
|
-
|
94
|
+
if target_env == 'draft' and is_local == False:
|
95
|
+
logger.error(f'For SAAS, please ensure this agent exists in a Live Environment')
|
96
|
+
exit(1)
|
97
|
+
|
61
98
|
|
62
99
|
|
100
|
+
return filtered_environments[0].get("id")
|
101
|
+
|
63
102
|
def get_tennent_id(self):
|
64
103
|
auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
|
65
104
|
|
66
105
|
cfg = Config()
|
67
106
|
active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
68
|
-
|
107
|
+
|
108
|
+
existing_auth_config = auth_cfg.get(AUTH_SECTION_HEADER).get(active_env, {})
|
109
|
+
|
110
|
+
existing_token = existing_auth_config.get(AUTH_MCSP_TOKEN_OPT) if existing_auth_config else None
|
111
|
+
token = jwt.decode(existing_token, options={"verify_signature": False})
|
112
|
+
crn = ""
|
113
|
+
crn = token.get('aud', None)
|
114
|
+
|
115
|
+
tenant_id = self.extract_tenant_id_from_crn(crn)
|
116
|
+
return tenant_id
|
117
|
+
|
118
|
+
|
119
|
+
|
120
|
+
def get_tenant_id_local(self):
|
121
|
+
auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
|
122
|
+
|
123
|
+
cfg = Config()
|
124
|
+
active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
|
69
125
|
|
70
126
|
existing_auth_config = auth_cfg.get(AUTH_SECTION_HEADER).get(active_env, {})
|
71
127
|
|
@@ -73,10 +129,7 @@ class ChannelsWebchatController:
|
|
73
129
|
|
74
130
|
token = jwt.decode(existing_token, options={"verify_signature": False})
|
75
131
|
tenant_id = ""
|
76
|
-
|
77
|
-
tenant_id = token.get('woTenantId', None)
|
78
|
-
else:
|
79
|
-
tenant_id = token.get('tenantId', None)
|
132
|
+
tenant_id = token.get('woTenantId', None)
|
80
133
|
|
81
134
|
return tenant_id
|
82
135
|
|
@@ -100,41 +153,70 @@ class ChannelsWebchatController:
|
|
100
153
|
|
101
154
|
|
102
155
|
def create_webchat_embed_code(self):
|
103
|
-
|
156
|
+
crn = None
|
157
|
+
is_ibm_cloud_env = is_ibm_cloud()
|
158
|
+
is_local = is_local_dev()
|
159
|
+
if is_ibm_cloud_env is True:
|
160
|
+
crn = input("Please enter your CRN which can be gotten from the IBM Cloud UI: ")
|
161
|
+
if crn == "":
|
162
|
+
logger.error("You must enter your CRN for IBM Cloud instances")
|
163
|
+
sys.exit(1)
|
164
|
+
is_crn_correct = self.check_crn_is_correct(crn)
|
165
|
+
if is_crn_correct == False:
|
166
|
+
logger.error("Invalid CRN format provided.")
|
167
|
+
|
168
|
+
if is_ibm_cloud_env and crn is not None:
|
169
|
+
tenant_id = self.extract_tenant_id_from_crn(crn)
|
170
|
+
elif is_ibm_cloud_env is False and is_local is False:
|
171
|
+
tenant_id = self.get_tennent_id()
|
172
|
+
elif is_local:
|
173
|
+
tenant_id = self.get_tenant_id_local()
|
104
174
|
host_url = self.get_host_url()
|
105
175
|
agent_id = self.get_agent_id(self.agent_name)
|
106
176
|
agent_env_id = self.get_environment_id(self.agent_name, self.env)
|
107
177
|
|
108
|
-
is_local = is_local_dev()
|
109
178
|
script_path = (
|
110
179
|
"/wxoLoader.js?embed=true"
|
111
180
|
if is_local
|
112
181
|
else "/wxochat/wxoLoader.js?embed=true"
|
113
182
|
)
|
114
183
|
|
184
|
+
config_lines = [
|
185
|
+
f'orchestrationID: "{tenant_id}"',
|
186
|
+
f'hostURL: "{host_url}"',
|
187
|
+
'rootElementID: "root"',
|
188
|
+
'showLauncher: true',
|
189
|
+
]
|
190
|
+
|
191
|
+
# Conditional fields for IBM Cloud
|
192
|
+
if is_ibm_cloud_env:
|
193
|
+
config_lines.append(f'crn: "{crn}"')
|
194
|
+
if is_ibm_cloud_env:
|
195
|
+
config_lines.append(f'deploymentPlatform: "ibmcloud"')
|
196
|
+
|
197
|
+
config_lines.append(f"""chatOptions: {{
|
198
|
+
agentId: "{agent_id}",
|
199
|
+
agentEnvironmentId: "{agent_env_id}"
|
200
|
+
}}""")
|
201
|
+
|
202
|
+
config_body = ",\n ".join(config_lines)
|
203
|
+
|
115
204
|
script = f"""
|
116
205
|
<script>
|
117
206
|
window.wxOConfiguration = {{
|
118
|
-
|
119
|
-
hostURL: "{host_url}",
|
120
|
-
rootElementID: "root",
|
121
|
-
showLauncher: true,
|
122
|
-
chatOptions: {{
|
123
|
-
agentId: "{agent_id}",
|
124
|
-
agentEnvironmentId: "{agent_env_id}"
|
125
|
-
}},
|
207
|
+
{config_body}
|
126
208
|
}};
|
127
209
|
|
128
210
|
setTimeout(function () {{
|
129
211
|
const script = document.createElement('script');
|
130
212
|
script.src = `${{window.wxOConfiguration.hostURL}}{script_path}`;
|
131
213
|
script.addEventListener('load', function () {{
|
132
|
-
|
214
|
+
wxoLoader.init();
|
133
215
|
}});
|
134
216
|
document.head.appendChild(script);
|
135
217
|
}}, 0);
|
136
218
|
</script>
|
137
|
-
|
219
|
+
"""
|
138
220
|
|
139
221
|
rich.print(script)
|
140
222
|
return script
|
@@ -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
|
|
@@ -191,31 +191,31 @@ def set_credentials_connection_command(
|
|
191
191
|
typer.Option(
|
192
192
|
'--client-id',
|
193
193
|
# help='For oauth_auth_on_behalf_of_flow, oauth_auth_code_flow, oauth_auth_implicit_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the client_id to authenticate against the application token server'
|
194
|
-
help='For oauth_auth_on_behalf_of_flow, the client_id to authenticate against the application token server'
|
194
|
+
help='For oauth_auth_on_behalf_of_flow and oauth_auth_client_credentials_flow, the client_id to authenticate against the application token server'
|
195
|
+
)
|
196
|
+
] = None,
|
197
|
+
client_secret: Annotated[
|
198
|
+
str,
|
199
|
+
typer.Option(
|
200
|
+
'--client-secret',
|
201
|
+
help='For oauth_auth_client_credentials_flow, the client_secret to authenticate with'
|
195
202
|
)
|
196
203
|
] = None,
|
197
|
-
# client_secret: Annotated[
|
198
|
-
# str,
|
199
|
-
# typer.Option(
|
200
|
-
# '--client-secret',
|
201
|
-
# help='For oauth_auth_code_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the client_secret to authenticate with'
|
202
|
-
# )
|
203
|
-
# ] = None,
|
204
204
|
token_url: Annotated[
|
205
205
|
str,
|
206
206
|
typer.Option(
|
207
207
|
'--token-url',
|
208
208
|
# help='For oauth_auth_on_behalf_of_flow, oauth_auth_code_flow, oauth_auth_password_flow and oauth_auth_client_credentials_flow, the url of the application token server'
|
209
|
-
help='For oauth_auth_on_behalf_of_flow, the url of the application token server'
|
209
|
+
help='For oauth_auth_on_behalf_of_flow and oauth_auth_client_credentials_flow, the url of the application token server'
|
210
|
+
)
|
211
|
+
] = None,
|
212
|
+
auth_url: Annotated[
|
213
|
+
str,
|
214
|
+
typer.Option(
|
215
|
+
'--auth-url',
|
216
|
+
help='For oauth_auth_code_flow, the url of the application token server'
|
210
217
|
)
|
211
218
|
] = None,
|
212
|
-
# auth_url: Annotated[
|
213
|
-
# str,
|
214
|
-
# typer.Option(
|
215
|
-
# '--auth-url',
|
216
|
-
# help='For oauth_auth_code_flow, oauth_auth_implicit_flow and oauth_auth_password_flow, the url of the application token server'
|
217
|
-
# )
|
218
|
-
# ] = None,
|
219
219
|
grant_type: Annotated[
|
220
220
|
str,
|
221
221
|
typer.Option(
|
@@ -223,6 +223,13 @@ def set_credentials_connection_command(
|
|
223
223
|
help='For oauth_auth_on_behalf_of_flow, the grant type used by the application token server'
|
224
224
|
)
|
225
225
|
] = None,
|
226
|
+
scopes: Annotated[
|
227
|
+
List[str],
|
228
|
+
typer.Option(
|
229
|
+
'--scopes',
|
230
|
+
help='For oauth_auth_code_flow and oauth_auth_client_credentials_flow, the optional scopes used by the application token server'
|
231
|
+
)
|
232
|
+
] = None,
|
226
233
|
entries: Annotated[
|
227
234
|
List[str],
|
228
235
|
typer.Option(
|
@@ -239,10 +246,11 @@ def set_credentials_connection_command(
|
|
239
246
|
token=token,
|
240
247
|
api_key=api_key,
|
241
248
|
client_id=client_id,
|
242
|
-
|
249
|
+
client_secret=client_secret,
|
243
250
|
token_url=token_url,
|
244
|
-
|
251
|
+
auth_url=auth_url,
|
245
252
|
grant_type=grant_type,
|
253
|
+
scopes=scopes,
|
246
254
|
entries=entries
|
247
255
|
)
|
248
256
|
|